3. 전처리기와 비트플래그

  1. 전처리기
    1. #define
    2. #error
    3. #if,-#elif,-#else,-#endif
    4. #ifdef,-#ifndef,-#undef
    5. #include
  2. 비트플래그



전처리기

전처리기 지시문은 소스 파일 맨 위에 위치하여 컴파일을 수행하기 전에 코드를 수정 및 조작하여 컴파일을 돕는다.

전차리기의 사용은 코드의 유지 보수를 용이하게 해 주며 효율성을 높이고 가독성을 늘린다.


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 변수보다 훨씬 효율적이며, 여러개의 상태값을 한번에 관리할 때 유용하다.