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 // 매크로 출력이 안된다.