C++ 전처리기: 지시자가 코드를 어떻게 형성하는가
전처리기(preprocessors)는 실제 컴파일이 시작되기 전에 정보를 전처리하도록 컴파일러에 지시하는 지시자(directives)들입니다.
모든 전처리기 지시자는 #으로 시작하며, 한 줄에서 전처리기 지시자 앞에는 공백 문자만 올 수 있습니다. 전처리기 지시자는 C++ 문장이 아니므로 세미콜론(;)으로 끝나지 않습니다.
모든 예제에서 본 #include 지시자를 이미 접했습니다. 이 매크로는 헤더 파일을 소스 파일에 포함하는 데 사용됩니다.
C++이 지원하는 전처리기 지시자는 #include, #define, #if, #else, #line 등 여러 가지가 있습니다. 중요한 지시자들을 살펴보겠습니다.
#define 전처리기
#define 전처리기 지시자는 심볼릭 상수를 생성합니다. 심볼릭 상수를 매크로(macro)라 부르며 지시자의 일반적인 형태는 다음과 같습니다.
#define macro-name replacement-text
이 줄이 파일에 나타나면, 해당 파일에서 이후의 매크로 모든 출현이 프로그램이 컴파일되기 전에 replacement-text로 대체됩니다. 예:
#include <iostream>
using namespace std;
#define PI 3.14159
int main() {
cout << "Value of PI :" << PI << endl;
return 0;
}
이제 이 코드를 전처리해 결과를 봅시다. 소스 코드 파일이 있다고 가정하고 -E 옵션으로 컴파일해 결과를 test.p로 리다이렉트합니다. test.p를 확인하면 많은 정보가 있고, 하단에서 값이 대체된 것을 찾을 수 있습니다.
$gcc -E test.cpp > test.p
...
int main() {
cout << "Value of PI :" << 3.14159 << endl;
return 0;
}
함수형 매크로
인자를 받는 매크로를 #define으로 정의할 수 있습니다.
Live Demo
#include <iostream>
using namespace std;
#define MIN(a,b) (((a)<(b)) ? a : b)
int main() {
int i, j;
i = 100;
j = 30;
cout << "The minimum is " << MIN(i, j) << endl;
return 0;
}
위 코드를 컴파일하고 실행하면:
The minimum is 30
조건부 컴파일
프로그램 소스 코드의 선택적 부분만 컴파일하는 데 사용할 수 있는 여러 지시자가 있습니다. 이 과정을 조건부 컴파일(conditional compilation)이라 합니다.
조건부 전처리기 구문은 'if' 선택 구조와 매우 유사합니다. 다음 전처리기 코드를 고려해 보세요.
#ifndef NULL
#define NULL 0
#endif
디버깅 목적으로 프로그램을 컴파일할 수 있습니다. 단일 매크로로 디버깅을 켜거나 끌 수도 있습니다.
#ifdef DEBUG
cerr << "Variable x = " << x << endl;
#endif
이렇게 하면 심볼릭 상수 DEBUG가 #ifdef DEBUG 지시자 전에 정의되었다면 cerr 문장이 프로그램에 컴파일됩니다. #if 0 문을 사용해 프로그램 일부를 주석 처리할 수 있습니다.
#if 0
compiling되지 않는 코드
#endif
다음 예제를 시도해 보세요.
Live Demo
#include <iostream>
using namespace std;
#define DEBUG
#define MIN(a,b) (((a)<(b)) ? a : b)
int main() {
int i, j;
i = 100;
j = 30;
#ifdef DEBUG
cerr << "Trace: Inside main function" << endl;
#endif
#if 0
/* 주석 처리된 부분 */
cout << MKSTR(HELLO C++) << endl;
#endif
cout << "The minimum is " << MIN(i, j) << endl;
#ifdef DEBUG
cerr << "Trace: Coming out of main function" << endl;
#endif
return 0;
}
위 코드를 컴파일하고 실행하면:
The minimum is 30
Trace: Inside main function
Trace: Coming out of main function
#와 ## 연산자
#과 ## 전처리기 연산자는 C++과 ANSI/ISO C에서 사용 가능합니다. # 연산자는 replacement-text 토큰을 따옴표로 둘러싸인 문자열로 변환합니다.
다음 매크로 정의를 고려해 보세요.
Live Demo
#include <iostream>
using namespace std;
#define MKSTR(x) #x
int main() {
cout << MKSTR(HELLO C++) << endl;
return 0;
}
위 코드를 컴파일하고 실행하면:
HELLO C++
어떻게 작동했는지 봅시다. C++ 전처리기가 다음 줄을
cout << MKSTR(HELLO C++) << endl;
다음 줄로 변환합니다.
cout << "HELLO C++" << endl;
## 연산자는 두 토큰을 연결하는 데 사용됩니다. 예:
#define CONCAT(x, y) x ## y
프로그램에서 CONCAT이 나타나면 인자들이 연결되어 매크로를 대체합니다. 예: CONCAT(HELLO, C++)는 프로그램에서 "HELLO C++"로 대체됩니다.
Live Demo
#include <iostream>
using namespace std;
#define concat(a, b) a ## b
int main() {
int xy = 100;
cout << concat(x, y);
return 0;
}
위 코드를 컴파일하고 실행하면:
100
작동 원리: C++ 전처리기가
cout << concat(x, y);
를 다음으로 변환합니다.
cout << xy;
미리 정의된 C++ 매크로
C++은 다음 미리 정의된 매크로들을 제공합니다.
| 번호 | 매크로 및 설명 |
| 1 | __LINE__ 프로그램 컴파일 시 현재 줄 번호 포함. |
| 2 | __FILE__ 프로그램 컴파일 시 현재 파일 이름 포함. |
| 3 | __DATE__ 소스 파일이 오브젝트 코드로 변환된 날짜인 month/day/year 형태 문자열 포함. |
| 4 | __TIME__ 프로그램이 컴파일된 시간인 hour:minute:second 형태 문자열 포함. |
위 모든 매크로 예제:
Live Demo
#include <iostream>
using namespace std;
int main() {
cout << "Value of __LINE__ : " << __LINE__ << endl;
cout << "Value of __FILE__ : " << __FILE__ << endl;
cout << "Value of __DATE__ : " << __DATE__ << endl;
cout << "Value of __TIME__ : " << __TIME__ << endl;
return 0;
}
위 코드를 컴파일하고 실행하면:
Value of __LINE__ : 6
Value of __FILE__ : test.cpp
Value of __DATE__ : Feb 28 2011
Value of __TIME__ : 18:52:48