조건부 포함

조건부 포함 (Conditional inclusion)

같은 소스 파일을 여러 환경에서 재사용하려면 "이 환경에서는 이 조각을 컴파일하고, 저 환경에서는 저 조각을" 같은 선택이 필요해요. 전처리기가 그 선택을 담당하는데, 그 중심에 서 있는 지시자가 #if·#ifdef 계열이에요. 소스의 일부를 조건에 따라 넣거나 빼는 이 기능을 '조건부 포함'이라고 불러요.

출처: cppreference

본문

문법 (Syntax)

#if          expression
#ifdef       identifier
#ifndef      identifier
#elif        expression
#elifdef     identifier        // (since C23)
#elifndef    identifier        // (since C23)
#else
#endif

설명 (Explanation)

조건부 전처리 블록은 #if, #ifdef, #ifndef 지시자로 시작해요. 그 뒤에는 원하는 만큼의 #elif, #elifdef, #elifndef(C23) 지시자, 선택적으로 최대 하나의 #else 지시자가 오고, 마지막에 #endif 지시자로 끝나요. 블록 안에 있는 중첩된 조건부 전처리 블록은 각각 따로 처리돼요.

#if, #ifdef, #ifndef 지시자는 지정된 조건을 검사하고, 그 값이 참이면 해당 코드 블록을 컴파일해요. 그 경우 뒤따르는 #else, #elifdef, #elifndef(C23), #elif 지시자는 무시돼요. 반대로 조건이 거짓이면 해당 블록은 건너뛰고, 뒤따르는 #else, #elifdef, #elifndef(C23), #elif 지시자 중 하나가 처리돼요.

  • 뒤따르는 지시자가 #else면, #else가 제어하는 코드 블록은 조건 없이 컴파일돼요.
  • 뒤따르는 지시자가 #elif, #elifdef, #elifndef(C23)면, 마치 #if 지시자인 것처럼 동작해요. 조건을 검사해 그 블록을 컴파일하거나 건너뛰고, 건너뛴 경우에는 다시 뒤따르는 #elif, #elifdef, #elifndef(C23), #else 지시자를 처리해요.

조건부 전처리 블록은 #endif 지시자로 끝나요.

조건 평가 (Conditional evaluation)

#if, #elif

표현식은 상수#define 지시자로 정의된 식별자만 사용하는 상수 표현식이에요. 리터럴도 아니고 #define으로 정의되지도 않은 식별자는 0으로 평가되며, true1로 평가돼요(C23).

표현식에는 defined 식별자 또는 defined (identifier) 형태의 단항 연산자가 들어갈 수 있어요. 이들은 식별자가 #define으로 정의되어 있으면 1, 아니면 0을 돌려줘요. 이 문맥에서 __has_include, __has_embed, __has_c_attribute는 정의된 매크로 이름처럼 취급돼요(C23). 표현식이 0이 아닌 값으로 평가되면 그 코드 블록이 포함되고, 아니면 건너뛰어져요. 사용된 식별자가 상수가 아니면 0으로 치환돼요.

전처리 지시자 문맥에서 __has_c_attribute 표현식은 주어진 속성 토큰이 지원되는지, 지원 버전이 무엇인지 검사해요. 자세한 내용은 속성 검사를 참고하세요. (C23)

참고: DR 412 이전에는 #if _cond1_ ... #elif _cond2_#if _cond1_ ... #else 다음에 #if _cond3_이 오는 형태와 달랐어요. 전자에서는 _cond1_이 참이면 두 번째 #if는 건너뛰어지므로 _cond3_이 잘 형성될 필요가 없지만, #elif_cond2_는 유효한 표현식이어야 했죠. DR 412 이후에는 건너뛰어진 코드 블록을 이끄는 #elif도 함께 건너뛰어져요.

결합 지시자 (Combined directives)

식별자가 매크로 이름으로 정의되었는지 검사해요.

  • #ifdef 식별자는 본질적으로 #if defined 식별자와 같아요.
  • #ifndef 식별자는 본질적으로 #if !defined 식별자와 같아요.
  • #elifdef 식별자는 본질적으로 #elif defined 식별자와 같아요.
  • #elifndef 식별자는 본질적으로 #elif !defined 식별자와 같아요. (C23)

주의할 점 (Notes)

#elifdef#elifndef 지시자는 C23을 대상으로 하지만, 구현들이 이를 **적합 확장(conforming extension)**으로 더 오래된 언어 모드에도 역이식(backport)할 수 있어요.

예제 (Example)

매크로 정의 여부와 값을 조합해 서로 다른 분기를 선택하는 예시예요.

#define ABCD 2
#include <stdio.h>

int main(void)
{

#ifdef ABCD
    printf("1: yes\n");
#else
    printf("1: no\n");
#endif

#ifndef ABCD
    printf("2: no1\n");
#elif ABCD == 2
    printf("2: yes\n");
#else
    printf("2: no2\n");
#endif

#if !defined(DCBA) && (ABCD < 2 * 4 - 3)
    printf("3: yes\n");
#endif

// C23 directives #elifdef/#elifndef
#ifdef CPU
    printf("4: no1\n");
#elifdef GPU
    printf("4: no2\n");
#elifndef RAM
    printf("4: yes\n"); // selected in C23 mode, may be selected in pre-C23 mode
#else
    printf("4: no3\n"); // may be selected in pre-C23 mode
#endif
}

가능한 출력:

1: yes
2: yes
3: yes
4: yes

같이 보기 (See also)

  • #define/#undef — 매크로 정의·해제
  • #error/#warning — 진단 지시자