Macros

매크로에는 object-like 매크로와 함수와 같이 생긴 function-like 매크로가 있습니다. 모두 #define 지시자를 이용해 정의하고 삭제할 때는 #undef 지시자를 이용합니다. 기본적으로 object-like 매크로와 function-like 매크로를 같은 이름으로 정의해 사용할 수 없습니다 ( warning 이 발생합니다 ). 매크로 이름이 quotes 안에 올 경우에는 확장이 되지 않습니다.

< object-like macro >            < function-like macro >

#define FOO 123                  #define BAR(a, b) ((a) + (b))    // 매크로 정의 

#undef FOO                       #undef BAR                       // 매크로 삭제

--------------------------------------------------------------

$ gcpp
#define BAR(a, b) ((a) + (b))
#define BAR 123                  // 동일한 이름으로 정의하면 warning 이 발생합니다.

BAR
@

<stdin>:2: warning: "BAR" redefined
<stdin>:1: note: this is the location of the previous definition

123
--------------------------------------------------------------

$ gcpp
#define FOO 123         // 매크로 정의
"FOO is: " FOO
'FOO is: ' FOO
#undef FOO              // 매크로 삭제
"FOO is: " FOO
@

"FOO is: " 123          # quotes 안에서는 매크로가 확장되지 않는다.
'FOO is: ' 123

"FOO is: " FOO          # 매크로 정의가 삭제되어 그대로 FOO 가 된다.
/usr/include/features.h

#ifdef _GNU_SOURCE
# undef  _ISOC95_SOURCE
# define _ISOC95_SOURCE    1
# undef  _ISOC99_SOURCE
# define _ISOC99_SOURCE    1
# undef  _ISOC11_SOURCE
# define _ISOC11_SOURCE    1
. . .

매크로 이름 작성법

매크로 이름은 C/C++ 식별자 ( identifiers ) 를 만드는 룰을 따릅니다. 그러니까 알파벳, 숫자, _ 만 사용이 가능하고 맨 앞에는 숫자가 올 수 없습니다. 보통 소스코드에서 사용되는 다른 식별자와 구분하기 위해 대문자로 작성을 합니다. 다음과 같은 이름들은 reserved names 으로 매크로 정의 시 사용하면 안 되겠습니다.

  • C standard library 에서 사용되는 이름들 ( 함수, 매크로 ... 코드를 읽는 다른 사람이 헷갈릴 수 있음 )

  • 식별자 이름이 two underscores 로 시작하는 경우

  • 식별자 이름이 underscore 로 시작하고 대문자가 오는 경우

  • global namespace 에서 underscore 로 시작하는 이름들

참고로 linux kenel 은 C standard library 를 사용하지 않기 때문에 이와 같은 제한이 없습니다.

Noncompliant Code

#ifndef _MY_FILE
#define _MY_FILE      // '_' 문자로 시작

#define __FIELD_VAL(field) foo##field     // "__" 두 개의 underscores 로 시작

#define free(a, b) ...     // free 는 standard library 함수 이름

#define _Num 100           // '_' 문자로 시작하고 뒤에 대문자

#endif

Compliant Code

#ifndef MY_FILE
#define MY_FILE

#define FIELD_VAL(field) foo##field

#define clean(a, b) ...

#define _num 100
#define num_ 100
#define Num_ 100

#endif

-. 문자는 매크로 이름을 구성하는데 사용되지 않으므로 첫 번째와 두 번째 foo 는 매크로 확장이 되지만 세 번째의 _foo 와 네 번째의 foo_ 는 각각 foo 와는 다른 매크로 이름이 되므로 확장되지 않습니다.

$ gcpp
#define foo bar()

foo->files          # '-' 문자는 매크로 이름에 사용되지 않는다.
foo.files
_foo->files         # _foo 와 foo_ 는 다른 매크로 이름이 된다.
foo_->files
@

bar()->files
bar().files
_foo->files
foo_->files

매크로는 One-line 으로 작성합니다.

C/C++ 언어는 statement 의 종료를 나타내는 문자로 ; 을 사용하기 때문에 line ending 개념이 없어서 여러 라인에 걸쳐서 작성해도 문제가 없지만 매크로는 line ending 개념이 있기 때문에 statement 가 얼마나 길어지던 상관없이 One-line 으로 작성해야 합니다. 만약에 multiple lines 으로 작성하고 싶으면 다음과 같이 backslash-newline 을 이용해 하나의 라인으로 만들어주어야 합니다.

backslash-newline 은 \ 를 이용해 newline 을 escape 하는것을 말합니다.

$ gcpp
#define WARN_IF(EXP) \
do { if (EXP) \
        fprintf (stderr, "Warning: " #EXP "\n"); } \
while (0)

WARN_IF(x == 0);
@

do { if (x == 0) fprintf (stderr, "Warning: " "x == 0" "\n"); } while (0);

Multiple lines 으로 매크로를 작성하더라도 전처리기를 통해 확장된 결과는 한 줄로 출력이 되기 때문에 가독성이나 디버깅하기가 어렵습니다. 이때는 다음과 같이 clang-format 을 이용하는 shell 함수를 작성해 사용하면 편리합니다.

format-macro ()
{
    if [ $# == 0 ]; then
        echo "ERROR: Source file required" >&2
        return 1
    fi
    file=$1
    sed -E '/^\s*#\s*(include\s*<[^>]+>)\s*$/{ s//\1;/; s/\//\a/g }' "$file" |
        cpp -P | clang-format -style="{BasedOnStyle: Google, IndentWidth: 4}" |
        sed -E '/^(include\s*<[^>]+>);$/{ s//#\1/; s/\a/\//g }'
}

$ format-macro parser.c > format_parser.c
$ gcc -g format_parser.c

매크로는 정의된 순서에 따라 처리됩니다.

따라서 다음의 경우 foo = AA 는 매크로 확장이 되지 않습니다.

$ gcpp
foo = AA;
#define AA 100
bar = AA;
@

foo = AA;
bar = 100;

TABLESIZE 매크로가 정의될 당시 BUFSIZE 는 1024 이지만 이후에 BUFSIZE 가 재정의되어 TABLESIZE 는 4096 으로 확장됩니다.

$ gcpp
#define BUFSIZE 1024
#define TABLESIZE BUFSIZE
#undef BUFSIZE
#define BUFSIZE 4096

TABLESIZE                // 현재 위치에서 TABLESIZE --> BUFSIZE --> 4096
@

4096

매크로 확장은 terminal token 이 나올 때까지 확장됩니다.

TABLESIZE 는 BUFSIZE 로 확장이 되고, 그다음 BUFSIZE 는 SIZE_1 로 확장이 되고 SIZE_1 는 최종적으로 terminal token 에 해당하는 1024 로 확장됩니다.

$ gcpp
#define TABLESIZE BUFSIZE
#define BUFSIZE SIZE_1
#define SIZE_1 1024

TABLESIZE
@

1024             # TABLESIZE ---> BUFSIZE ---> SIZE_1 ---> 1024

current->files 에서 current 는 get_current() 로 확장이 되고 다시 get_current()(current_thread_info()->task) 로 확장이 됩니다.

$ gcpp
#define get_current() (current_thread_info()->task)
#define current get_current()

int __get_unused_fd_flags(unsigned flags, unsigned long nofile)
{
    return __alloc_fd(current->files, 0, nofile, flags);
}
@

int __get_unused_fd_flags(unsigned flags, unsigned long nofile)
{
    return __alloc_fd((current_thread_info()->task)->files, 0, nofile, flags);
}

다음의 경우 DEFINE_XARRAY(foobar)DEFINE_XARRAY_FLAGS(foobar, 0) 로 확장이 되는데 확장 결과를 살펴보면 매칭되는 매크로 DEFINE_XARRAY_FLAGS 가 존재하므로 struct xarray name = XARRAY_INIT(foobar, 0) 로 확장이 되고, 확장 결과를 살펴보면 매칭되는 매크로 XARRAY_INIT 가 존재하므로 다시 확장됩니다.

$ gcpp
#define XARRAY_INIT(name, flags) { \
    .xa_lock = __SPIN_LOCK_UNLOCKED(name.xa_lock), \
    .xa_flags = flags, \
    .xa_head = NULL, \
}

#define DEFINE_XARRAY_FLAGS(name, flags) \
    struct xarray name = XARRAY_INIT(name, flags)

#define DEFINE_XARRAY(name) DEFINE_XARRAY_FLAGS(name, 0)

DEFINE_XARRAY(foobar)
@

struct xarray foobar = { .xa_lock = __SPIN_LOCK_UNLOCKED(foobar.xa_lock),
.xa_flags = 0, .xa_head = NULL, }

CPP 는 기본적으로 recursion 이 안됩니다.

매크로는 terminal token 이 나올 때까지 연이어서 확장이 되는데 그중에 확장 결과로 자기 자신이 나오게 되면 더 이상 확장되지 않습니다. ( 물론 다른 매크로는 계속 확장됩니다. )

$ gcpp
#define foo (4 + foo)

foo
@

(4 + foo)       # 무한 loop 를 방지하기 위해 더이상 확장이 안된다.

-----------------------------------------------------------------

$ gcpp
#define x (4 + y)
#define y (2 * x)

x                           1. x        --->  (4 + y) 로 확장되고
@                           2. (4 + y)  --->  (4 + (2 * x)) 로 확장되고
                            3. (4 + (2 * x)) 는 더이상 확장되지 않는다.
(4 + (2 * x))

-----------------------------------------------------------------

$ gcpp
#define foo bar
#define bar foo 200

foo bar                     1. foo  --->  bar  --->  foo 200  로 확장되고
@                              foo 자신은 더이상 확장되지 않는다.
                            2. bar  --->  foo 200  --->  bar 200  로 확장되고
foo 200 bar 200                bar 자신은 더이상 확장되지 않는다.

enum 과 매크로가 동일한 이름을 사용할 경우

enum 과 매크로가 동일한 이름을 사용할 수 있는데요. 이때는 다음과 같이 매크로를 정의해야 #ifdef 에서도 매크로를 사용할 수 있고, enum 이름이 삭제되는 것을 방지할 수 있습니다.

#define FOO FOO

--------------------

$ gcpp                                          $ gcpp
enum { FOO, BAR }                               enum { FOO, BAR }
#define FOO                                     #define FOO FOO

#ifdef FOO                                      #ifdef FOO
val += FOO                                      val += FOO
#endif                                          #endif
@                                               @

enum { FOO, BAR }   전처리기에 의해                 enum { FOO, BAR }
val +=       <----  enum 이름이 삭제된다.           val += FOO
// /usr/include/x86_64-linux-gnu/bits/socket_type.h 참조

enum __socket_type
{
  SOCK_STREAM = 1,     /* Sequenced, reliable, connection-based byte streams. */
#define SOCK_STREAM SOCK_STREAM
  SOCK_DGRAM = 2,      /* Connectionless, unreliable datagrams of fixed maximum length. */
#define SOCK_DGRAM SOCK_DGRAM
  SOCK_RAW = 3,        /* Raw protocol interface. */
#define SOCK_RAW SOCK_RAW
. . .

CPP 에서는 매크로가 재정의 될 경우 warning 이 발생합니다.

기존에 매크로가 정의되어 있는데 다시 다른 값으로 재정의 할 경우 warning 이 발생합니다. 그래야 의도치 않게 매크로가 재정의 되는지 알 수 있겠죠. 재정의 되더라도 값이 같으면 warning 은 발생하지 않습니다.

warning 은 발생하지만 매크로 값은 새로 정의됩니다.

# 동일한 이름의 매크로가 재정의되어 warning 이 발생한다.

$ gcpp
#define BUFSIZE 1024
#define BUFSIZE 4096

BUFSIZE
@
<stdin>:2: warning: "BUFSIZE" redefined
<stdin>:1: note: this is the location of the previous definition

4096          # warning 은 발생하지만 값은 새로 정의된다.

-------------------------------------------

# 재정의하려면 먼저 #undef 을 해야 합니다.

#define BUFSIZE 1024
#undef BUFSIZE
#define BUFSIZE 4096

정의 내용이 같다는 것은 공백으로 분리되는 token 들이 모두 같다는 것입니다.
whitespace 는 전처리기에서 token 으로 사용되지 않으므로 empty 와 같습니다.

### 매크로 정의가 같은 경우 ###

#define FOUR (2 + 2)                   // (2 , + , 2)    token 들이 모두 같다.
#define FOUR         (2    +    2)     // (2 , + , 2)
#define FOUR (2 /* two */ + 2)         // (2 , + , 2)    주석은 제외된다.

### 매크로 정의가 다른 경우 ###

#define FOUR (2 + 2)           // (2 , + , 2)    token 들이 모두 틀리다.
#define FOUR ( 2+2 )           // ( , 2+2 , )
#define FOUR (2 * 2)           // (2 , * , 2)
#define FOUR() (2 + 2)         // 같은 이름의 function-like 매크로를 동시에 정의할 수 없다.

gcc 명령 라인에서도 매크로를 정의할 수 있습니다.

매크로를 정의할 때는 -D 옵션을 사용하고 = 를 이용해 값을 지정하지 않으면 기본적으로 1 로 설정됩니다.

$ gcpp -D __foo -D __bar=200
#ifdef __foo
"__foo" is : __foo
"__bar" is : __bar
#endif
@

"__foo" is : 1              # __foo 값은 1 이 된다.
"__bar" is : 200

C standard library 헤더를 사용할 때 _GNU_SOURCE 매크로를 정의하면 gnu 에서 제공되는 각종 nonstandard 확장 함수를 사용할 수 있습니다 ( /usr/include/features.h 파일 참조 ).

$ cat /usr/include/stdlib.h
. . .
extern void qsort (void *__base, size_t __nmemb, size_t __size,
           __compar_fn_t __compar) __nonnull ((1, 4));
#ifdef __USE_GNU
extern void qsort_r (void *__base, size_t __nmemb, size_t __size,
             __compar_d_fn_t __compar, void *__arg)
  __nonnull ((1, 4));
#endif
. . .

$ cpp -M <<< "#include <stdlib.h>" 
-: /usr/include/stdc-predef.h /usr/include/stdlib.h \
 /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
 /usr/include/features.h   <-----
. . .

$ grep -P -C1 'define[ \t]+__USE_GNU' /usr/include/features.h 
#ifdef  _GNU_SOURCE
# define __USE_GNU      1
#endif

# _GNU_SOURCE 매크로가 정의되면 qsort_r 함수를 사용할 수 있다.

$ cpp <<< "#include <stdlib.h>" | grep qsort
extern void qsort (void *__base, size_t __nmemb, size_t __size,

$ cpp -D_GNU_SOURCE <<< "#include <stdlib.h>" | grep qsort
extern void qsort (void *__base, size_t __nmemb, size_t __size,
extern void qsort_r (void *__base, size_t __nmemb, size_t __size,

Quiz

다음은 socket 프로그래밍에서 read, write 을 할때 각 단계별로 소켓 버퍼의 상태를 출력해주는 매크로를 사용하는 예입니다. 테스트를 끝내고 매크로 출력을 제거하려면 어떻게할까요?

#define CHECK_BUF_SZ( $fd ) do { \
        size_t size = 0; \
        printf("buffer size : "); \
        ioctl($fd, FIONREAD, &size); \
        printf("IN: %lu", size); \
        ioctl($fd, TIOCOUTQ, &size); \
        printf(" / OUT: %lu\n", size); \
} while (0)

    . . .
    CHECK_BUF_SZ(sockfd);        // 매크로를 이용해 현재 소켓 버퍼 상태를 출력

    ret = send(sockfd, buf, sizeof buf, MSG_DONTWAIT);
    printf("send... done : ret = %d\n", ret);

    CHECK_BUF_SZ(sockfd);
    sleep(5);

    ret = recv(sockfd, buf, sizeof buf, MSG_DONTWAIT);
    printf("recv... done : ret = %d\n", ret);

    CHECK_BUF_SZ(sockfd);
    . . .

#########  output  #########

buffer size : IN: 0 / OUT: 0              <---- 매크로 출력
send... done : ret = 2586539
buffer size : IN: 0 / OUT: 2553798
recv... done : ret = 81280
buffer size : IN: 202 / OUT: 2415282

다음과같이 CHECK_BUF_SZ 매크로를 #undef 한후에 body 가 empty 인 매크로를 다시 정의해주면 됩니다.

#define CHECK_BUF_SZ( $fd ) do { \
        size_t size = 0; \
        printf("buffer size : "); \
        ioctl($fd, FIONREAD, &size); \
        printf("IN: %lu", size); \
        ioctl($fd, TIOCOUTQ, &size); \
        printf(" / OUT: %lu\n", size); \
} while (0)

#undef  CHECK_BUF_SZ                 // 먼저 CHECK_BUF_SZ 매크로를 #undef 하고
#define CHECK_BUF_SZ(fd)             // body 가 empty 인 매크로를 다시 정의
    . . .
    CHECK_BUF_SZ(sockfd);

    ret = send(sockfd, buf, sizeof buf, MSG_DONTWAIT);
    printf("send... done : ret = %d\n", ret);
    . . .
#########  output  #########

send... done : ret = 2586539        // 매크로 출력이 제거된다.
recv... done : ret = 81280

또한 컴파일시 DEBUG 매크로 정의 여부에 따라 출력을 조정하려면 다음과 같이 해주면 됩니다.

#ifndef DEBUG
#undef  CHECK_BUF_SZ
#define CHECK_BUF_SZ(a)
#endif

$ gcc -D DEBUG test.c       // 매크로가 적용되어 출력된다.
$ gcc test.c                // 매크로 출력이 안된다.