3. 전처리기와 비트플래그
전처리기
전처리기 지시문은 소스 파일 맨 위에 위치하여 컴파일을 수행하기 전에 코드를 수정 및 조작하여 컴파일을 돕는다.
전차리기의 사용은 코드의 유지 보수를 용이하게 해 주며 효율성을 높이고 가독성을 늘린다.
1. #define
#define 지시문은 #define (식별자) (문자열)
로 구성돠며, 컴파일러가 컴파일을 진행할 때 식별자를 만나면 문자열로 대체한다.
이 때 주의할 점은 연산이 이루어지는 것이 아니라 식별자를 문자열로 대체 하는 것이기 때문에 만약 수식을 #define
지시문에 사용한다면 연산 우위에 주의해야 한다.
#define TEN 10
#define PLUS(a, b) a + b
int main()
{
int i = 0, j = 0, k = 0;
i += TEN; //컴파일 과정에서 TEN 은 10 으로 대체된다. 결과, i에는 10이 할당된다.
j = PLUS(TEN, 5); //컴파일 과정에서 10 + 5 로 대체된다. 이후 연산하여 j에 15가 할당된다.
k = PLUS(TEN, 5) * 2; //컴파일 과정에서 10 + 5 * 2로 대체된다. 연산 우선순위에 의해 k에는 20이 할당된다.
return 0;
}
위 코드와 같은 연산 오류를 방지하기 위해, ()를 적극적으로 활용해 주는 것이 좋다.
2. #error
#error 지시문은 #error (문자열)
로 구성되며, 컴파일 중 해당 지시문을 만나면 오류 메시지를 내보내고 컴파일을 종료한다.
다음과 같이 사용하여 C에서 컴파일되어 오류를 일으킬 때 컴파일을 중단하고 오류 메시지를 간결하게 출력할 수 있다.
#if !defined(__cplusplus)
#error C++ compiler required.
#endif
3. #if, #elif, #else, #endif
#if 지시문은 조건부로 컴파일을 제어한다. #if (조건문)
에서 조건문이 참(non-zero)일 경우, 지시문 뒤의 줄을 컴파일한다.
조건문의 참/거짓 판별은 #if
- #elif
- … - #elif
- #else
순서로 진행된다.
#include <iostream>
using namespace std;
#if 0 //조건문 거짓, 컴파일 되지 않음
int main()
{
cout << "case1\n";
return 0;
}
#elif 1 //조건문 참, 컴파일 실행
int main()
{
cout << "case2\n";
return 0;
}
#else //조건문이 앞의 #elif 에서 종료됨, 컴파일 되지 않음
int main()
{
cout << "case3\n";
return 0;
}
#endif //조건부 지시문 종료
위의 경우 main() 함수가 3개가 있지만, 첫번째 main()과 세번째 main()은 컴파일되지 않으므로 문제 없이 빌드된다.
이처럼 코드의 다른 버전이나 플랫폼 별 코드를 만들 때 등에 유용하게 쓰일 수 있다.
4. #ifdef, #ifndef, #undef
#ifdef 와 #ifndef 지시문은 각각 #if defined
, #if !defined
와 동일한 효과를 가지며, 뒤에 오는 매크로의 정의 여부에 따라 조건부 컴파일을 수행한다.
#undef 지시문은 이전에 정의된 매크로를 취소하는 데 사용된다.
#define TEN 10
int main()
{
#ifdef TEN //TEN 이 정의되어 있을 경우 실행
cout << "TEN is defined\n";
#else //TEN 이 정의되어 있지 않을 경우 실행
cout << "TEN is not defined\n";
#endif //TEN 에 대한 조건부 지시문 종료
#undef TEN //TEN 식별자의 정의를 취소
return 0;
}
5. #include
#include 지시문은 지정된 파일의 내용을 포함하도록 지시하는 전처리기이다.
상수 또는 매크로 정의를 포함하는 파일(이를 헤더 파일 이라고 한다)을 #include <(헤더파일명)>
또는 #include "(헤더파일명)"
으로 파일 최상단에 입력하여 헤더 파일 안에 포함된 내용을 소스 파일에 추가한다.
큰따옴표(““)로 지정된 파일은 현재 디렉토리 내부의 파일을 검색 후 시스템 경로의 헤더 파일을 검색하고, 홑화살괄호(<>)로 지정된 파일은 시스템 경로의 헤더 파일만을 검색한다.
따라서 사용자 정의 헤더파일은 #include "(헤더파일명)"
, 시스템에서 주어지는 헤더파일은 #include <(헤더파일명)>
로 사용한다고 알아두면 편하다.
비트플래그
비트플래그를 이용하면 여러 기능들을 구현할 때, 각각의 bit 칸이 대응되는 기능의 T/F 스위치로 동작하게 할 수 있다.
특정 비트자리에 값을 넣거나(\|
, 비트연산자 OR), 비트 값을 0으로 초기화하거나(a&(~a)
, 비트연산자 AND 와 NOT 이용), 특정 자리의 값을 확인(&
응용)하는 등 여러 용도로 사용이 가능하다.
//버프
#define ATKSPD_UP 0x1 //1 << 0
#define ATK_UP 0x2 //1 << 1
#define DEF_UP 0x4 //1 << 2
#define CRITICALRATE_UP 0x8 //1 << 3
#define DMG_UP 0x10 //1 << 4
#define EXP_UP 0x20 //1 << 5
#define STR_UP 0x40 //1 << 6
#define DEX_UP 0x80 //1 << 7
#define INT_UP 0x100 //1 << 8
#define LUK_UP 0x200 //1 << 9
#define FINALDMG_UP 0x400 //1 << 10
#define DOT_HEAL 0x800 //1 << 11
#define ALL_STATUS_UP (STR_UP|DEX_UP|INT_UP|LUK_UP)
앞서 언급된 전처리기 #define
을 사용하여 각 식별자를 정의하고 16진수 표기로 각 비트 자리에 1을 할당한다. 16진수 표기법 외에도 주석과 같이 비트시프트를 이용해 표기할 수도 있다.
위의 경우 총 12개의 상태를 저장하기 위해 12bit가 필요하므로 2byte 이상의 자료형 변수에 할당하여 데이터를 관리할 수 있다.
비트플래그는 1byte를 사용해서 T/F 를 저장하는 bool
변수보다 훨씬 효율적이며, 여러개의 상태값을 한번에 관리할 때 유용하다.