C언어에는 전처리기가 있다.

 

define, include 가 있는데 이번 포스팅에서는 define 에 대해서 알아본다.

 

전처리기란 무엇인가? 전처리기는 영어로 preprocessor 이다. pre 전에 processor 프로세서 한다는 것인데 C에서는 컴파일 전에 처리하는 것을 의미한다.

 

프로그램의 실행 절차를 생각해보자.

 

[프로그램 설계와 작성 program design and writing)

1. 사람이 소스코드(source code)를 작성한다

 

[컴파일 시간 compile time]

2. C언어 컴파일러(compiler)가 컴파일을 한다. (기계어로 변환된 목적코드)

3. 링커(linker)가 링크를 한다. (실행파일)

 

[실행시간 runtime]

4. 운영체제의 로더(loader)가 C언어 프로그램을 메모리에 로드한다.

5. 프로세스가 제어를 가지고 작동한다.(메모리에 생성된 프로그램 : 프로세스)

 

컴파일러는 2번 컴파일 시간이 되기 전에 전처리기를 처리한다. 그래서 컴파일의 전처리기다.

 

* 우선은 쉬운 예제를 본다.

 

#define 이름 값

 

의 형식으로 사용한다. 이때 문장의 끝에 세미콜론 ; 은 입력하지 않는다.

 

컴파일러는 이것을 기억하고 실제 컴파일을 하는 도중에 AGE 를 만나면 정수 10으로 바꾸어서 컴파일한다.

#include <stdio.h>

#define AGE 10

int main(int argc, char const *argv[])
{
    printf("AGE : %d", AGE);
    
    return 0;
}

컴파일러는 소스코드를 이렇게 바꾼다. 이것을 매크로를 치환한다고 한다.

printf("AGE : %d", 10);

 

* 정수 뿐만 아니라 수식도 사용할 수 있다. 이를 매크로 함수라고 한다.

#define SQUARE(x) (x * x)


int main(int argc, char const *argv[])
{
    int a = 5;
    int b = 3;

    printf("SQUARE(%d) : %d\n", a, SQUARE(a));
    printf("SQUARE(%d) : %d\n", b, SQUARE(b));

    // printf("SQUARE(%d) : %d\n", a+b, SQUARE(a+b));

    return 0;
}

매크로 함수는 사용하기에 따라서 성능향상을 가져올 수 있는데 이는 위에서 설명한 것처럼 컴파일 전에 처리한 코드이기 때문이다. 일반 함수는 실행시간에 메모리에 변수를 생성하고 연산을 하고 가져오는데 시간이 소모된다. 매크로 함수는 그런 과정을 생략하고 바로 프로그램에 값을 전달하기 때문에 속도가 향상될 수 있다.

(항상 그런 것은 아니다. 프로그램의 속성에 따라 다르다.)

 

* 주의할 점은 매크로 치환은 일반 함수와 다른 규칙을 갖기 때문에 아래 코드를 실행시키면 잘못된 값을 얻게된다.

#define SQUARE(x) (x * x)

int main(int argc, char const *argv[])
{
    int a = 5;
    int b = 2;

    printf("SQUARE(%d) : %d\n", a+b, SQUARE(a+b));

    return 0;
}

SQUARE(a+b) 를 치환하면

 

(x * x) 가 된다. 이는

 

(a + b * a + b) 이고 (5 + 2 * 5 + 2) 로 바뀌면

 

연산자 우선순위에 의하여 5 + 10 + 2 = 17 이 나온다.

 

우리가 기대한 것은 7 * 7 = 49 인데 차이가 난다. 이런 경우 괄호를 씌우면 제대로 된 답 49를 얻을 수 있다.

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

(5+2) * (5+2) 가 된다.

 

* 매크로 함수 내에서 함수를 사용할 수도 있다. 

#define LINE    printf("----------------------------------\n")
#define WELCOME printf("---------- Hello World! ---------- \n")

int main(int argc, char const *argv[])
{
    LINE;
    WELCOME;
    LINE;
    return 0;
}

 

마치 새로운 문법을 만드는 느낌이 든다. define 매크로를 사용하면 다양한 기법을 사용할 수 있다.

 

* 변수명 출력하기(문자열 출력) # 연산자

 

그동안 변수의 이름을 지어서 사용했는데 정작 변수의 이름은 어떻게 출력할 수 있을까? # 연산자는 괄호안의 문자열을 "" 따옴표로 감싸준다. 

 

TO_STRING(value) -> "value"

 

일반 함수에서는 func(x)는 매개변수의 전달을 의미한다. #을 사용한 매크로에서는 단순히 문자로 치환한다.

 

일반 함수로는 불가능한 변수의 이름을 출력할 수 있다.

#define TO_STRING(name) #name

int main(int argc, char const *argv[])
{
    int value;
    long data;

    printf("variable name : %s\n", TO_STRING(value));
    printf("variable name : %s\n", TO_STRING(data));

    return 0;
}

 

* 함수의 기능을 정지시키는 매크로

 

아래의 코드처럼 #define (함수이름) 을 사용하면 아무 동작도 하지 않는다. 그 라인은 건너뛰고 다른 코드는 작동을 한다. 디버그 등의 작업시에 유용하다.

 

#define putchar
#define LINE    printf("----------------------------------\n")

int main(int argc, char const *argv[])
{
    LINE;
    putchar('c');
    LINE;
    return 0;
}

 

* 토큰연산자 ## 사용하기

 

아래의 매크로에 사용된 ##으로 숫자를 만들 수 있다. 예를 들면 1과 2를 붙이면 12 이런 식이다. 매크로 함수의 매개변수로 사용할 수도 있다.

 

#define CONCAT_NUMBER(a,b) a##b
#define SQUARE(x) ((x) * (x))
#define LINE    printf("----------------------------------\n")

int main(int argc, char const *argv[])
{   
    int a = 2;
    int b = 3;

    LINE;
    printf("CONCAT_NUMBER : %d\n", CONCAT_NUMBER(1,2));
    printf("SQUARE(CONCAT_NUMBER) : %d\n", SQUARE(CONCAT_NUMBER(1,2)));
    LINE;
    return 0;
}

 

* 매크로의 연산자를 사용하여 여러가지 시도를 할 수 있다. 매크로는 고급의 주제라서 C언어 교재에서도 뒷부분에 가르친다. 고수가 되기 위해서 거쳐야 하는 관문이다.

 

#define GEN_TOKEN(n) printf("token" #n " = %d\n", token##n)

int main(int argc, char const *argv[]){

    int token7 = 99;

    GEN_TOKEN(7);
 
    return 0;
}

 

define 이 갖는 치환이라는 개념은 일반적 함수를 다루는 것과는 다르다는 점을 염두해 두고 프로그래밍을 하면 좋을 것이다.

공유하기

facebook twitter kakaoTalk kakaostory naver band