반복 inclusion 방지를 위한 전처리기 지시어: #ifndef, #ifdef, #define

C/C++에서 헤더 파일을 여러 번 포함할 경우 같은 식별자(함수, 변수, 타입 등)가 중복 정의되어 링크 에러나 컴파일 에러가 발생할 수 있습니다. 이 문제를 방지하기 위해 전처리기 조건부 포함(conditional inclusion) 기법이 사용되며, 핵심은 #ifndef, #define, #endif의 조합입니다.

#define: 심볼 정의

#define은 간단한 매크로 식별자를 정의하는 전처리기 명령입니다. 함수를 호출하지 않고도 미리 정의된 코드 조각을 삽입할 수 있습니다. 정의 시점에 치환되므로 추가 실행 오버헤드가 없습니다.

#define BUFFER_SIZE 1024
#define SQUARE(x) ((x) * (x))

첫 예제는 상수 치환, 두 번째는 인자에 대한 매크로 함수입니다. 단, 괄호 생략 시 연산자 우선순위로 인해 예기치 않은 결과가 나올 수 있어 각별한 주의가 필요합니다.

#ifndef: 심볼 미정의時 코드 포함

#ifndef SYMBOL은 "SYMBOL이 정의되지 않았다면"이라는 조건을 의미합니다. 주로 header guard(헤더 가드) 형태로 사용됩니다:

#ifndef MY_HEADER_H
#define MY_HEADER_H

// 헤더 안의 실제 코드 (함수 원형, 클래스 정의 등)

#endif // MY_HEADER_H

첫 포함 시 MY_HEADER_H는 정의되지 않았으므로 define으로 정의한 후 내용을 포함합니다. 두 번째부터는 이미 정의되었으므로 #ifndef 블록 전체가 무시됩니다. 이를 통해 동일 헤더의 중복 포함로 인한多重 정의를 완전히 방지합니다.

#ifdef: 심볼 정의시 코드 포함

#ifdef SYMBOL은 "SYMBOL이 이미 정의되어 있다면"이라는 뜻입니다. 디버그/릴리스 구분, 플랫폼별 코드 분기, 기능 옵션 제어 등에 단독 또는 #else와 함께 사용됩니다:

#ifdef DEBUG
    log("Debug mode is on");
#else
    // 최적화된 릴리스 코드
#endif

이 예시는 DEBUG가 #define DEBUG로 명시되었을 때만 디버그 로그 코드를 포함하게 합니다. 릴리스 빌드 시 해당 #define을 제거하면 전부 생략되어 코드 크기와 실행 시간을 줄일 수 있습니다.

#if + #defined(): 유연한 조건 처리

정의 여부와 별개로 상수 표현식 또는 조합 조건이 필요할 땐 #if와 defined()를 조합합니다:

#if defined(_WIN32) && !defined(__CYGWIN__)
    // Windows 전용 구현
#elif defined(__linux__)
    // Linux 전용 구현
#endif

또는 심볼 값에 따라 동작을 달리할 수 있습니다:

#define LOG_LEVEL 2

#if LOG_LEVEL > 1
    verbose_log("Detailed trace");
#endif

실제 헤더 가드 추천 패턴

가장 일반적이고 안정적인 헤더 가드 양식은 다음처럼すること입니다:

#ifndef UTILS_MATH_H
#define UTILS_MATH_H

namespace utils {
    inline double hypotenuse(double a, double b) {
        return std::sqrt(a*a + b*b);
    }
}

#endif // UTILS_MATH_H

이 방식은 헤더가 여러 번 포함되더라도 중복 정의 없이 안전하게 동작합니다. 특히 C++에서 전역 변수나 정적 객체가 헤더 본문에 포함되면 대부분의 경우 오류가 발생하므로, 헤더 가드는 필수적으로 적용되어야 합니다.

반면, C 언어에서는 동일한 함수/변수 선언의 중복은 오류가 아니므로 헤더 가드가 없어도 인클루드 자체는 가능합니다. 하지만 정의(아니 선언만이 아니라 실제 구현)가 포함된 경우는 반드시 헤더 가드가 필요합니다.

태그: #ifndef #ifdef #define 전처리기 헤더가드

10월 4일 19:55에 게시됨