76.2 값 또는 식으로 조건부 컴파일하기

이번에는 #if로 값 또는 식을 판별하여 조건부 컴파일을 해보겠습니다.

#if 값 또는 식
코드
#endif

다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요.

conditional_compile_expression.c

#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는 전처리기 과정을 거치면 코드는 다음과 같이 바뀝니다.

그림 76‑2 #if 조건부 컴파일

#ifdefined와 조합하면 복잡한 조건도 만들 수 있습니다.

#if defined 매크로
코드
#endif

다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요.

conditional_compile_logical_operator.c

#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이 정의되어 있지 않을 때(!) 코드를 컴파일합니다.

#if (defined DEBUG || defined TEST) && !defined (VERSION_10)
    printf("Debug\n");
#endif

defined 뒤의 괄호는 생략해도 됩니다. 그리고 논리 연산의 순서를 명확하게 나타내기 위해 defined와 논리 연산자를 모두 괄호로 묶습니다.

참고 | 조건부 컴파일과 디버그 코드

#if는 다음과 같이 DEBUG 매크로를 정의해서 사용합니다. #define DEBUG 1과 같이 코드에서 1과 0을 지정하여 디버그 코드의 사용 여부를 제어하거나, 컴파일 옵션에서 DEBUG 매크로를 설정하여 디버그 코드를 제어합니다.

  • Visual Studio: 솔루션 탐색기에서 프로젝트 선택 > 메인 메뉴의 프로젝트(P) > 속성(P) > C/C++ > 전처리기 > 전처리기 정의
  • GCC: -D매크로이름, -D매크로이름=값
$ gcc main.c -DDEBUG
$ gcc main.c -DDEBUG_LEVEL=2
#define DEBUG 1  // 코드에서 디버그 코드 제어

#if DEBUG
    printf("Debug message\n");
#endif

#ifdef DEBUG     // 코드에 DEBUG 매크로를 정의하거나 컴파일 옵션에서 DEBUG 매크로 설정
    printf("Debug message\n");
#endif

// 코드에서 2를 DEBUG_LEVEL 정의하거나 컴파일 옵션에서 DEBUG_LEVEL에 2를 설정
#ifdef DEBUG_LEVEL >= 2
    printf("Debug Level 2\n");
#endif

이번에는 #elif#else를 사용해보겠습니다.

#ifdef 매크로
코드
#elif defined 매크로
코드
#else
코드
#endif

#if 조건식
코드
#elif 조건식
코드
#else
코드
#endif

다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요

conditional_compile_elif_else.c

#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;
}

실행 결과

USB

#elif를 사용하여 PS2가 정의되어 있지 않고, USB가 정의되어 있을 때 코드를 컴파일하고, #else를 사용하여 PS2USB 둘 다 정의되어 있지 않을 때 코드를 컴파일하도록 만들었습니다. 여기서는 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가 전처리기 과정을 거치면 코드는 다음과 같이 바뀝니다.

그림 76‑3 #ifdef, #elif, #else 조건부 컴파일

#ifndef는 매크로가 정의되어 있지 않을 때 코드를 컴파일합니다.

#ifndef 매크로
코드
#endif

다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요.

conditional_compile_undefined.c

#include <stdio.h>

#define NDEBUG    // NDEBUG 매크로 정의

int main()
{
#ifndef DEBUG     // DEBUG가 정의되어 있지 않을 때 코드를 컴파일
    printf("main function\n");
#endif

    return 0;
}

실행 결과

main function

여기서는 NDEBUG만 정의되어 있고 DEBUG는 정의되어 있지 않습니다. 따라서 #ifndef DEBUG를 만족하므로 코드를 컴파일 합니다. 참고로 NDEBUG는 "not debug"를 뜻합니다.

#ifndef#elif, #else와 함께 사용할 수 있습니다.