C++ 소스파일은 특별한 지시문에 의해서 기계어로 번역되기 전에 변경될 수 있다. 번역되기 전에 소스가 결정(수정)되는 작업이어서 '전처리'라고 부르며 전처리기(Preprocessor)에 의해서 수행된다.
C++에서는 #으로 시작하는 지시문(directives)에 으해서 전처리를 지시한다.
ex) #include, #define, #if~#else#endif 등이 대표적인 전처리 지시문
소스파일에 헤더 파일의 내용을 그대로 포함하는 기능을 수행한다. 헤더 파일에는 주로 소스 파일에서 사용하는 타입, 클래스, 변수, 함수와 관련된 정보가 수록된다.
#define은 매크로를 정의하는 지시문이다. #define은 소스 코드에 사용된 매크로를 전개하여 대체하는 역할을 수행한다.
#define의 형식은 다음과 같이 두 종류가 있다.
#define은 소스 코드에서 정의된 identifier가 나타날 때마다 token-list로 대체하도록 지시하는 역할을 하는데,여기서 이 identifier가 token-list로 대체되는 것을 '매크로 전개'라고 부른다. 추가로 token-list란 토큰이 0개 이상 나열된 것을 말한다.
하지만 매크로 전개가 항상 일어나는 것은 아니다. identifier가 주석이나 문자열에 사용될 경우에는 매크로 전개가 되지 않으며, 오직 identifier가 토큰으로 사용되는 경우에만 매크로 전개가 일어난다. 아래의 매크로 상수 예시를 보자.
// 예제 1-7 매크로 상수
#include <iostream>
using namespace std;
#define PI 3.141592 // PI는 identifier
void main()
{
double d = PI; // 8행 : PI가 사용되는 부분(매크로 O)
cout << "PI is " << PI << endl; // 9행 : PI가 사용되는 부분(매크로 X)
}
위의 소스코드에서는 8, 9행에서 PI가 사용되었지만, PI가 3.141592로 매크로 전개되는 경우에는 8행 뿐이다. 8행에서 identifier PI는 토큰(여기서는 대입되는 값)으로 사용되기 때문에 매크로 전개가 일어난다. 반면 9행의 PI는 문자열("PI is ")에 포함되어 있으므로 토큰이 아니기 때문에 매크로 전개가 일어나지 않는다.
매크로 함수란 identifier가 마치 함수처럼 사용되는 것을 말한다. 아래의 매크로 함수 예시를 보자.
// 예제 1-8 매크로 함수
#include <iostream>
using namespace std;
#define MULTIPLY(x, y) ((x)*(y)) // MULTIPLY가 바로 매크로 함수
void main()
{
cout << MULTIPLY(1+1, 1+2) << endl;
cout << MULTIPLY(1+1, 1+2) / MULTIPLY(1+1, 1+2) << endl;
}
매크로 함수 MULTIPLY는 두 인자를 받은 후 곱한 값으로 매크로 전개가 일어난다. 여기서 주의할 점은 모든 인자와 매크로 정의를 괄호로 묶어줘야 한다는 것이다. 매크로 함수는 기계적으로 전개 되기 때문에 만약에 아래와 같이 매크로 함수를 정의할 경우 오답이다.
#include <iostream>
using namespace std;
#define MULTIPLY(x, y) x*y
조건부 컴파일 지시문은 #define으로 정의된 매크로를 조건으로 소스 파일에 포함 여부를 결정한다.
// 예제 1-9 조건부 컴파일 지시문
#define PIPE_ORGAN // 1행
void main()
{
#ifdef PIPE_ORGAN // 5행
PlayPipeOrgan(); // 6행
#else
PlayPiano(); // 8행
#endif
}
1행에서 #define에 의해서 PIPE_ORGAN을 정의한다. 눈여겨볼 점은 token-list가 없다는 사실이다. 이럴 경우 매크로 PIPE_ORGAN은 매크로 전개에 사용되지는 못하지만 조건부 컴파일 지시문의 조건으로 사용될 수 있다.
5행의 #ifdef가 바로 PIPE_ORGAN의 정의 여부를 판별한다. 만약 PIPE_ORGAN이 정의되어 있을 경우 소스 파일에는 6행이 포함되며, 정의되어 있지 않을 경우 소스 파일에 8행이 포함된다. PIPE_ORGAN이 정의되지 않았을 때의 코드가 불필요하다면 7,8행은 생략 가능하다. 즉, #if ~ #endif만 사용 가능하다. 반대로 정의되지 않은 경우만 지시하고 싶을 경우에는 #ifdef 대신 #ifndef를 사용하면 된다.
매크로는 보통 사용자가 정의하지만 기본적으로 미리 제공되는 매크로도 있다.
ex)
// 예제 1-10 미리 정의된 매크로
#include <iostream>
using namespace std;
void PrintSystem()
{
cout << __FILE__ << endl;
cout << __LINE__ << endl;
cout << __FUNCTION__ << endl;
#ifdef _M_X64 // 10행
cout << "64Bit x64 Windows!" << endl;
#else
cout << "32Bit x86 Windows!" << endl;
#endif
}
10행의 _M_X64는 Visual Studio가 빌드하는 타깃 플랫폼이 64비트 x64일때 정의된다.