68. 조건부 컴파일과 파일 포함
1. 키워드
- 조건부 컴파일
2. 조건부 컴파일과 파일 포함 사용하기
- 조건부 컴파일은 정해진 조건에 따라 소스 코드의 컴파일 여부를 제어하는 전처리기이다.
1] 플랫폼과 OS 구분
- 조건부 컴파일은 플랫폼이나 OS에 특화된 코드를 구분할 때 사용한다.
- 예를 들어 Windows API로 작성된 코드는 리눅스에서 컴파일할 수 없으므로 조건부 컴파일을 사용하여
WIN32
매크로가 있을 때만 컴파일되도록 만든다.
2] 디버깅 및 테스트
- 최종 제품에는 디버깅을 위한 코드나 테스트 코드가 들어가면 안 되므로 조건부 컴파일을 사용하여 코드를 분리한다.
#include
도 전처리기이며 헤더 파일이나 소스 파일을 현재 소스 코드에 포함하여 함께 컴파일하는 기능이다.- 보통 특정한 기능을 이용하기 위해 헤더 파일을 포함할 때 사용하며 조건부 컴파일과 연계하여 플랫폼과 OS에 특화된 헤더 파일을 포함한다.
3. 조건부 컴파일 사용하기
- 조건부 컴파일은
#ifdef
와#endif
지시자를 사용하여 정의한다. #ifdef
에 매크로를 지정하면 해당 매크로가 정의되어 있을 때만 코드를 컴파일한다.
#include <stdio.h>
#define DEBUG // DEBUG 매크로 정의
int main()
{
#ifdef DEBUG // DEBUG 매크로가 정의되어 있다면 #ifdef, #endif 사이의 코드를 컴파일
printf("Debug: %s %s %s %d\n", __DATE__, __TIME__, __FILE__, __LINE__);
#endif
return 0;
}
- 먼저
#define
으로DEBUG
라는 매크로를 정의한다. - 이때 조건부 컴파일에 사용할 매크로는 매크로 이름만 정의해도 된다.
- 그리고 다음과 같이 조건부 컴파일을 할 코드를
#ifdef
,#endif
로 묶어준다. - 여기서
#ifdef DEBUG
는DEBUG
매크로가 정의되어 있다면#ifdef
,#endif
사이의 코드를 컴파일한다.
#ifdef DEBUG // DEBUG 매크로가 정의되어 있다면 #ifdef, #endif 사이의 코드를 컴파일
printf("Debug: %s %s %s %d\n", __DATE__, __TIME__, __FILE__, __LINE__);
#endif
printf
안에서 사용한__DATE__
,__TIME__
,__FILE__
,__LINE__
은 컴파일러에서 제공하는 매크로이며 디버그 코드를 작성할 때 유용하다.
1] __DATE__
- 컴파일한 날짜(실행 시점의 현재 날짜가 아님)
2] __TIME__
- 컴파일한 시간(실행 시점의 현재 시간이 아님)
3] __FILE__
__FILE__
매크로가 사용된 헤더, 소스 파일
4] __LINE__
__LINE__
매크로가 사용된 줄 번호
#ifdef
,#endif
가 전처리기 과정을 거치면 코드는 다음과 같이 바뀐다.
4. 값 또는 식으로 조건부 컴파일하기
- 이번에는
#if
로 값 또는 식을 판별하여 조건부 컴파일을 해보자.
#include <stdio.h>
#define DEBUG_LEVEL 2 // 2를 DEBUG_LEVEL로 정의
int main()
{
#if DEBUG_LEVEL >= 2 // DEBUG_LEVEL이 2보다 크거나 같으면 #if, #endif 사이의 코드를 컴파일
printf("Debug Level 2\n");
#endif
#if 1 // 조건이 항상 참이므로 #if, #endif 사이의 코드를 컴파일
printf("1\n");
#endif
#if 0 // 조건이 항상 거짓이므로 #if, #endif 사이의 코드를 컴파일하지 않음
printf("0\n");
#endif
return 0;
}
// Debug Level 2
// 1
- 이번에는
2
를DEBUG_LEVEL
매크로로 정의했다. - 그리고
#if DEBUG_LEVEL >= 2
와 같이 정의했으므로DEBUG_LEVEL
이2
보다 크거나 같을 때 코드를 컴파일한다.
#if DEBUG_LEVEL >= 2 // DEBUG_LEVEL이 2보다 크거나 같으면 #if, #endif 사이의 코드를 컴파일
printf("Debug Level 2\n");
#endif
#if
에는 값을 그대로 지정할 수도 있다.- 여기서
1
을 지정하면 조건이 항상 참이므로 코드를 컴파일하고,0
을 지정하면 조건이 항상 거짓이므로 코드를 컴파일하지 않는다.
#if 1 // 조건이 항상 참이므로 #if, #endif 사이의 코드를 컴파일
printf("1\n");
#endif
#if 0 // 조건이 항상 거짓이므로 #if, #endif 사이의 코드를 컴파일하지 않음
printf("0\n");
#endif
#if
,#endif
는 전처리기 과정을 거치면 코드는 다음과 같이 바뀐다.
#if
는defined
와 조합하면 복잡한 조건도 만들 수 있다.
#include <stdio.h>
#define DEBUG // DEBUG 매크로 정의
#define TEST // TEST 매크로 정의
int main()
{
// DEBUG 또는 TEST가 정의되어 있으면서 VERSION_10이 정의되어 있지 않을 때
#if (defined DEBUG || defined TEST) && !defined(VERSION_10)
printf("Debug\n");
#endif
return 0;
}
// Debug
#if defined
에는 논리 연산자를 사용할 수 있다.- 여기서는
DEBUG
또는(||
)TEST
가 정의되어 있으면서(&&
)VERSION_10
이 정의되어 있지 않을 때(!
) 코드를 컴파일한다.
defined
뒤의()
(괄호)는 생략해도 된다.- 그리고 논리 연산의 순서를 명확하게 나타내기 위해
defined
와 논리 연산자를 모두()
로 묶는다.
조건부 컴파일과 디버그 코드
#if
는 다음과 같이DEBUG
매크로를 정의해서 사용한다.#define DEBUG 1
과 같이 코드에서1
과0
을 지정하여 디버그 코드의 사용 여부를 제어하거나, 컴파일 옵션에서DEBUG
매크로를 설정하여 디버그 코드를 제어한다.
- 이번에는
#elif
와#else
를 사용해 보자.
#include <stdio.h>
#define USB // USB 매크로 정의
int main()
{
#ifdef PS2 // PS2가 정의되어 있을 때 코드를 컴파일
printf("PS2\n");
#elif defined USB // PS2가 정의되어 있지 않고, USB가 정의되어 있을 때 코드를 컴파일
printf("USB\n");
#else // PS2와 USB가 정의되어 있지 않을 때 코드를 컴파일
printf("지원하지 않는 장치입니다.\n");
#endif
return 0;
}
#elif
를 사용하여PS2
가 정의되어 있지 않고,USB
가 정의되어 있을 때 코드를 컴파일하고,#else
를 사용하여PS2
와USB
둘 다 정의되어 있지 않을 때 코드를 컴파일하도록 만들었다.- 여기서는
USB
만 정의되어 있으므로#elif defined USB
만 만족한다.
#ifdef PS2 // PS2가 정의되어 있을 때 코드를 컴파일
printf("PS2\n");
#elif defined USB // PS2가 정의되어 있지 않고, USB가 정의되어 있을 때 코드를 컴파일
printf("USB\n");
#else // PS2와 USB가 정의되어 있지 않을 때 코드를 컴파일
printf("지원하지 않는 장치입니다.\n");
#endif
#elif
로 매크로를 판별할 때는defined
와 함께 사용해야 한다.#ifdef
,#elif
,#else
가 전처리기 과정을 거치면 코드는 다음과 같이 바뀐다.
#ifndef
는 매크로가 정의되어 있지 않을 때 코드를 컴파일한다.
#include <stdio.h>
#define NDEBUG // NDEBUG 매크로 정의
int main()
{
#ifndef DEBUG // DEBUG가 정의되어 있지 않을 때 코드를 컴파일
printf("main function\n");
#endif
return 0;
}
// main function
- 여기서는
NDEBUG
만 정의되어 있고DEBUG
는 정의되어 있지 않다. - 따라서
#ifndef DEBUG
를 만족하므로 코드를 컴파일한다. #ifndef
도#elif
,#else
와 함께 사용할 수 있다.
5. 파일 포함하기
#include
앞에#
이 쓰인 이유는#include
도 전처리기이기 때문이다.#
은 전처리기를 호출하는 문법이다.#include
지시자는 헤더 파일(.h
)과 소스 파일(.c
)를 포함한다.
#include
에 헤더 파일을 지정할 때<>
(홑화살괄호)를 사용하기도 하고,""
(큰따옴표)를 사용하기도 한다.<>
와""
의 차이는 다음과 같다.
1] <>
- 보통 C 표준 라이브러리의 헤더 파일을 포함할 때 사용한다.
- 또한, 컴파일 옵션에서 지정한 헤더 파일 경로를 기준으로 파일을 포함한다.
2] ""
- 현재 소스 파일을 기준으로 헤더 파일을 포함하고, 헤더 파일을 찾지 못 할 경우 컴파일 옵션에서 지정한 헤더 파일 경로를 따른다.
- 예를 들어,
"message.h"
는 현재 소스 파일과 같은 경로에 있는message.h
파일을 포함한다. - 그리고
"inc/message.h"
는 현재 소스 파일 경로에서inc
디렉터리 아래의message.h
파일을 포함한다. - 마지막으로
"../message.h"
는 현재 소스 파일 경로의 상위 디렉터리에 있는message.h
파일을 포함한다.
- 헤더 파일을 만들 때는 확장자명을
.h
로 지정하면 된다. - 이번에는 작업 디렉터리에
message.h
헤더 파일을 생성하여 추가한다. - 헤더 파일이 추가되었으면
message.h
파일에 다음과 같이 입력한다.
#if defined EN
#define HELLO_MESSAGE "Hello"
#elif defined KO
#define HELLO_MESSAGE "안녕하세요"
#elif defined FR
#define HELLO_MESSAGE "Bonjour"
#endif
- 이제 다음 내용을 입력한 뒤 실행해 보자.
#include <stdio.h>
#define KO // KO 매크로 정의
#include "message.h" // message.h 헤더 파일 포함
int main()
{
printf("%s\n", HELLO_MESSAGE); // message.h에 정의한 HELLO_MESSAGE의 값이 출력됨
// KO 매크로가 정의되어 있으므로 HELLO_MESSAGE는 "안녕하세요"가 됨
return 0;
}
// 안녕하세요
#include
를 사용하여message.h
헤더 파일을 포함했다.
- 현재 소스 코드에는
HELLO_MESSAGE
매크로가 없지만message.h
헤더 파일 안에HELLO_MESSAGE
매크로가 정의되어 있으므로 다음과 같이 매크로의 값을 출력할 수 있다. - 여기서
#include
위에#define
으로KO
를 정의했으므로message.h
에도KO
가 적용되어#elif KO
부분의#define HELLO_MESSAGE "안녕하세요"
가 컴파일된다. - 이때
#include
아래에KO
를 정의하면 적용되지 않으므로 주의한다.
printf("%s\n", HELLO_MESSAGE); // message.h에 정의한 HELLO_MESSAGE의 값이 출력됨
// KO 매크로가 정의되어 있으므로 HELLO_MESSAGE는 "안녕하세요"가 됨
- 다른 언어를 사용하고 싶다면
#include "message.h"
위에KO
대신EN
이나FR
을 정의하면 된다. - 지금까지 C 표준 함수의 보안 경고로 인한 컴파일 에러를 방지하기 위해
#include <stdio.h>
위에#define _CRT_SECURE_NO_WARNINGS
처럼 정의해 준 것도 같은 원리이다. - 즉,
_CRT_SECURE_NO_WARNINGS
가 정의되어 있으면 Visual Studio에서 제공하는 함수 대신 C 표준 함수를 선택해서 사용한다. - 이처럼 헤더 파일을 포함하면 헤더 파일 안에 정의된 매크로나 선언된 함수 등을 사용할 수 있다.
#include
는#ifdef
,#if
등과 조합하여 특정 조건에서만 헤더 파일을 포함할 수 있다.
#define DEBUG
#ifdef DEBUG // DEBUG가 정의되어 있다면 debug_message.h 헤더 파일 포함
#include "debug_message.h"
#else // 아니면 message.h 헤더 파일 포함
#include "message.h"
#endif
- 지금까지 조건부 컴파일과 파일 포함에 대해 배웠는데 코드에 매크로가 섞이다 보니 알아보기가 쉽지 않았다.
- 실제로 C로 작성된 프로그램의 소스 코드를 보면 매크로와 조건부 컴파일을 상당히 많이 사용한다.
- 특히 멀티 플랫폼을 지원하는 프로그램은 매크로와 조건부 컴파일을 복잡하게 사용한다.