본문 바로가기

윤성우의 C Programming

Chapter 26. 매크로와 선행처리기 (Preprocessor)

1. 선행처리기와 매크로 

 

실행파일은 컴파일 이전에 '선행처리' 라는 과정을 거치게 된다.선행 처리 명령문은 # 으로 시작을 하며, 컴파일러로 처리되는게 아니라 선행처리기로 처리되기 때문에 뒤에 세미콜론을 붙이지 않는다. 또한 컴파일러는 선행처리기의 명령을 이해하지 못한다. 

#include <stdio.h>

 

컴파일 과정을 거치게 되면 소스코드가 운영체제가 이해할 수 있는 바이너리 데이터로 이루어진 오브젝트 파일이 생성된다. 그렇다면 컴파일 이전에 이루어지는 선행처리기에서는 어떤 처리가 이루어지는 걸까? 

 

 

 

선행처리의 과정을 거쳐서 생성되는 파일도 그냥 소스코드일 뿐이다. 단지 선행처리 명령문대로 소스코드 일부를 수정하는 단순 치환(substitution) 의 과정을 거칠 뿐이다. 

2.  대표적인 선행처리 명령문 

#define: Object-like macro   객체와 같은 매크로

영단어 "object" 는 그 자체로 완전한 의미를 갖는 대상이나 사물을 의미한다. A = B 처럼  변수명이나 상수값을 대체하는 단순한 텍스트 치환으로 작동한다.  

#define PI 3.14159

 

#define: Function-like macro  함수와 같은 매크로

함수 호출처럼 매개변수를 가질 수 있으며, 이 매개변수들을 사용해 보다 복잡한 표현식이나 코드 조각을 정의할 수 있다. 

참고로 선행처리기는 + 연산을 못하기 때문에 괄호를 쳐서 나누기 연산, 더하기 연산이 먼저 이루어지지 않게 해야한다.

#define SQUARE(x) ((x) * (x))

매크로 두 줄에 걸쳐서 정의하려면 ? 

개행문자 \ 를 사용해서 줄이 바뀌었음을 명시해야 한다. 

#define SQUARE(X)      \
((X)*(X))

 

매크로 정의 시, 먼저 정의된 매크로 사용 가능하다 

먼저 정의된 매크로는 뒤에서 매크로를 정의할 때 사용 가능하다. 

#include <stdio.h>

// 객체와 같은 매크로: PI를 정의
#define PI 3.14159

// 함수와 같은 매크로: CIRCLE_AREA를 정의, 먼저 정의된 PI 매크로를 사용
#define CIRCLE_AREA(radius) (PI * (radius) * (radius))

int main() {
    float radius = 5.0;

    // 함수와 같은 매크로 CIRCLE_AREA 사용하여 원의 넓이 계산
    float area = CIRCLE_AREA(radius);

    printf("Radius: %.2f, Area: %.2f\n", radius, area);

    return 0;
}

매크로의 장단점 

장점 

  • 매크로 함수는 일반 함수에 비해 실행속도가 빠르다. 
  • 자료형에 따라서 별도로 함수를 정의하지 않아도 된다. 

매크로 함수는 선행처리기에 의해서 매크로 함수의 몸체부분이 매크로 함수의 호출 문장을 대신하기 떄문에 일반 함수 호출 시 아래와 같은 과정이 없다. 

 

  • 호출된 함수를 위한 스택 메모리의 할당 
  • 실행위치의 이동과 매개변수로의 인자 전달 
  • return 문에 의한 값의 반환 

또한 컴파일 전 단계에서 수행하기 때문에 자료형 상관없이 제대로 치환된다. 

 

단점 

  • 정의하기가 정말 까다로움
  • 디버깅하기 쉽지 않음

매크로 함수는 대소문자를 구분하고, 괄호를 잘 쳐야한다. 예를 들어서 두 값의 차를 계산하는 매크로를 정의하려면 다음과 같이 써야 한다. 

#define DIFF_ABD(X,Y)  ((X)>(Y)?(X)(-Y):(Y)-(X))

 

매크로를 잘못 정의하면, 에러 메시지는 선해어리 이전의 소스파일을 기준으로 출력되지 않고, 선행처리 이후의 소스파일을 기준으로 출력이 된다. 따라서 일반적인 에러 메시지보다 이해하기 힘들다는 단점이 있다. 

 

결론적으로 함수 매크로는 다음과 같은 상황에 사용하는 것이 적절하다. 

 

  • 작은 크기의 함수 
  • 호출의 빈도수가 높은 함수 

3. 조건부 컴파일(Conditional Compilation)을 위한 매크로 

이 지시어들을 사용하면 컴파일러에게 특정 조건 하에서만 코드를 컴파일하도록 지시할 수 있다. 

#if...#endif: 참이라면 

#if 지시어는 주어진 조건이 참(true)일 때만 코드 블록을 컴파일하도록 한다.
#endif#if 조건 블록의 끝을 나타낸다. 조건에 안맞으면 다 지워버린다.

#include <stdio.h>

#define LEVEL 2

int main() {
    #if LEVEL == 0
    printf("Level is 0.\n");
    #elif LEVEL == 1
    printf("Level is 1.\n");
    #elif LEVEL == 2
    printf("Level is 2.\n");
    #else
    printf("Level is unknown.\n");
    #endif

    return 0;
}
Level is 2.

#ifdef...#endif: 정의만 되어있다면 

참인지 거짓인지 상관없이 정의만 되어있다면 함수를 출력한다. 

 

#include <stdio.h>

// DEBUG 매크로를 정의. 이 줄을 주석 처리하거나 제거하면, 'Debug mode is off.'가 출력된다.
#define DEBUG

int main() {
    #ifdef DEBUG
    printf("Debug mode is on.\n");
    #else
    printf("Debug mode is off.\n");
    #endif

    return 0;
}
Debug mode is off.

#ifndef...#endif: 정의되지 않았다면 

이 매크로는 주로 헤더파일의 중복포함을 막기 위해 주로 사용한다. 

#include <stdio.h>

// FEATURE_ENABLE 매크로를 정의하려면 다음 줄의 주석을 제거하세요.
// #define FEATURE_ENABLE

int main() {
    #ifndef FEATURE_ENABLE
    printf("Feature is disabled.\n");
    #else
    printf("Feature is enabled.\n");
    #endif

    return 0;
}
Feature is enabled

 

#else 의 삽입: #if, #ifdef, #ifndef 에서 사용할 수 있음 

 

#if와 #else 사용 예제

#include <stdio.h>

#define LEVEL 3

int main() {
    #if LEVEL > 5
    printf("Level is greater than 5.\n");
    #else
    printf("Level is 5 or less.\n");
    #endif

    return 0;
}

 

#ifdef와 #else 사용 예제

#include <stdio.h>

// 다음 줄의 주석을 제거하여 DEBUG를 정의해 보세요.
// #define DEBUG

int main() {
    #ifdef DEBUG
    printf("Debug mode is ON.\n");
    #else
    printf("Debug mode is OFF.\n");
    #endif

    return 0;
}

#ifndef와 #else 사용 예제

#include <stdio.h>

// 다음 줄의 주석을 제거하여 FEATURE를 정의해 보세요.
// #define FEATURE

int main() {
    #ifndef FEATURE
    printf("Feature is disabled.\n");
    #else
    printf("Feature is enabled.\n");
    #endif

    return 0;
}

#elif의 삽입: #if 에만 해당

#include <stdio.h>

#define DEBUG_LEVEL 2

int main() {
#if DEBUG_LEVEL == 1
    printf("Debug level 1 is enabled\n");
#elif DEBUG_LEVEL == 2
    printf("Debug level 2 is enabled\n");
#else
    printf("Debug mode is disabled\n");
#endif

    return 0;
}

 

4. 매개변수의 결합과 문자열화 

# 연산자로 문자열 내의 매크로의 매개변수 치환하기 

문자열 내에서는 매크로의 매개변수 치환이 발생하지 않는다. 이를 해결하려면 #연산자를 사용하면 된다. 

#define STR(ABC) #ABC

//매개변수 ABC에 전달되는 인자를 문자열 "ABC"로 치환해라

 

문자열은 나란히 선언하면 하나의 문자열로 간주된다. 

 

예시 1

 

예시 2

필요한 형태대로 단순하게 결합하기 : 매크로 ## 연산자 

#include <stdio.h>

#define CON(UP, LOW) UP ## 00 ## LOW

int main() {
    int result = CON(12, 34);
    printf("Result: %d\n", result);

    return 0;
}
120034