Conditionals
전처리기 조건 지시자는 특정 코드나 지시자를 선택적으로 제외하거나 포함할 때 사용합니다.
machine 또는 operating system 에 따라 설정을 달리해 컴파일해야 될 때
같은 소스코드로 다른 프로그램을 만들고자 할 때 ( 테스트 코드나, 디버깅 정보 출력을 포함하거나 제외 )
특정 코드 영역을 삭제하지 않고 나중에 참조하기 위해 제외하려고 할 때 등
#ifdef, #ifndef
#ifdef
, #ifndef
지시자는 특정 매크로가 현재 정의되어 있는지 테스트할 때
사용합니다.
다음과 같이 object-like 매크로, function-like 매크로 모두 #ifdef
에서 참이 됩니다.
$ gcpp $ gcpp $ gcpp
#define foo #define foo 100 #define foo()
#ifdef foo #ifdef foo #ifdef foo
111 111 111
#endif #endif #endif
@ @ @
111 111 111
같은 소스코드를 컴파일 하더라도 machine 또는 operating system 에 따라
predefined macros 가 다를 수 있습니다.
또한 명령행 상에서 -D
옵션을 이용해 매크로를 define 하고 -U
옵션을 이용해 undefine
할 수도 있습니다.
다음은 #ifndef
지시자를 이용해서 Once-Only Headers 를 구현하는 예입니다.
// foo.h 파일 내용
// 처음 foo.h 파일이 include 될때 FILE_FOO_H 매크로가 #define 되므로
// 두 번째부터는 #ifndef 지시자에 의해 다시 include 되지 않게 됩니다.
#ifndef FILE_FOO_H
#define FILE_FOO_H
the entire file
#endif
#if
# if Expression // C expression of integer type
controlled text . . .
# endif
#ifdef
지시자는 단순히 특정 매크로가 정의되어 있는지 테스트 하지만
#if
지시자는 expression 을 다룹니다.
expression 에서는 C 언어에서 사용되는 대부분의 연산자를 사용할 수 있습니다.
( ++, --, 포인터 관련, 대입연산자 들은 제외 ).
다음은 #if
,#else
조건 지시자를 이용해 선택적으로 매크로를 정의하는
예인데 &&
, ||
, !
논리연산자, <
, >
비교연산자, a ? b : c
삼항연산자 들이
사용되고 있는 것을 볼 수 있습니다.
// __USE_GNU 매크로가 정의되어 있고 && (defined __cplusplus ? (...) : (...)) 가 참이면
// __GLIBC_USE_DEPRECATED_SCANF 매크로가 1 로 정의되고 그렇지 않으면 0 으로 정의됩니다.
#if (defined __USE_GNU \
&& (defined __cplusplus \
? (__cplusplus < 201103L && !defined __GXX_EXPERIMENTAL_CXX0X__) \
: (!defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L)))
# define __GLIBC_USE_DEPRECATED_SCANF 1
#else
# define __GLIBC_USE_DEPRECATED_SCANF 0
#endif
expression 에 매크로가 사용되면 먼저 확장이 완료된 후 연산이 됩니다.
정의되지 않은 매크로 사용, 0
은 거짓과 같고 그 외 값은 참이 됩니다.
body 가 empty 인 매크로를 사용하면 expression 이 empty 가 되므로 오류가 됩니다.
기본적으로 전처리 단계에서는 C/C++ 언어의 타입 정보는 알 수 없으므로 sizeof 연산자나
enum 값은 사용할 수 없습니다.
expression 에서 사용할 수 있는 값들은 다음과 같습니다.
- integer constants
- character constants ( 'A' 는 65 와 같다 )
defined
전처리기 연산자의 사용- 정의되지 않은 매크로는
0
과 같습니다. - double quotes 은 오류에 해당합니다.
$ gcpp $ gcpp $ gcpp $ gcpp -Dfoo
#if foo #define foo #define foo bar #if foo
#define NUM 100 #if foo #if foo #define NUM 200
#endif #define NUM 200 #define NUM 200 #endif
#endif #endif
value: NUM value: NUM
@ value: NUM value: NUM @
@ @
value: NUM <stdin>:2:8: error: value: 200
#if with no expression value: NUM
# 정의되지 않은 # foo 가 bar 로 확장되고
# 매크로는 value: NUM # bar 는 정의되지 않은
# 0 과 같다. # 매크로가 된다.
----------------------------------------------------------------------------------
$ gcpp $ gcpp $ gcpp
#define foo #define foo (1 - 1) #define foo (1 + 1)
#if defined(foo) #if foo #if foo
#define NUM 200 #define NUM 200 #define NUM 100
#endif #endif #endif
value: NUM value: NUM value: NUM
@ @ @
value: 200 value: NUM value: 100
-------------------------------------------------------------------------------------
$ gcpp $ gcpp $ gcpp
#define foo "bar" #define foo 'bar' #define foo 'b'
#if foo #if foo #if foo
#define NUM 200 #define NUM 200 #define NUM 200
#endif #endif #endif
value: NUM value: NUM value: NUM
@ @ @
<stdin>:1:13: error: <stdin>:2:5: warning:
token ""bar"" is not valid multi-character character value: 200
in preprocessor expressions constant [-Wmultichar]
value: NUM value: 200
#if 0
( always-false )
소스코드의 특정 영역을 삭제하지 않고 나중에 참조하기 위해 남겨놓으려 할 경우,
/*...*/
( block comment ) 는 nesting 해서 사용할 수가 없기 때문에 오류가 발생할 수 있는데
이때 #if 0
을 활용할 수 있습니다.
#if 0
에 의해 코드가 제외된다고 하더라도 quotes 이나 /*...*/
는 balance 가 맞아야 합니다.
#if 0
"controlled"
'text' // ' or " 가 하나만 있으면 warning 이 되고
/* controlled text */ // /* 하나만 있으면 error 가 된다.
#endif
defined
defined
는 전처리기 연산자로 #if
, #elif
지시자를 사용할 때 특정 매크로가
현재 정의되어 있는지 테스트할 때 사용합니다.
#ifdef MACRO
는 사실상 #if defined MACRO
와 같은 것입니다.
logical not 은 앞에 !
를 붙이면 됩니다.
#if defined FOO || defined (BAR) // 괄호는 제외하거나 포함할 수 있다.
정의되지 않은 매크로는 0
과 같습니다.
따라서 다음 첫 번째 식은 두 번째와 같이 줄일 수 있습니다.
#if defined BUFSIZE && BUFSIZE >= 1024
#if BUFSIZE >= 1024
#elif
#elif
지시자 뒤에는 #else
가 와도 되고 안 와도 됩니다.
#else
#else
지시자는 #if
, #elif
뿐만 아니라 #ifdef
, #ifndef
지시자 와도 함께 사용할 수 있습니다.
#if
, #else
가 중첩될 경우 적용되는 순서는 다음과 같습니다.
$ gcpp
#define FEATURE_A
#if defined (FEATURE_A)
AAA
#if defined (FEATURE_B)
BBB
#if defined (FEATURE_C)
CCC
#else
ELSE_CCC
#endif
#else
ELSE_BBB
#endif
#else
ELSE_AAA
#endif
@
AAA
ELSE_BBB
#endif
#if
, #ifdef
, #ifndef
지시자를 사용할 때 종료를 나타냅니다.
#if __linux__ // #if
#define OFFSET 9
#define OFFSIZ 8
#define MAP "maps"
#define PTX long
#define DIFF_T void *
#elif __FreeBSD__ // #elif
#define OFFSET 10
#define OFFSIZ 9
#define MAP "map"
#define PTX int
#define DIFF_T caddr_t
#define PTRACE_TRACEME PT_TRACE_ME
#define PTRACE_PEEKTEXT PT_READ_I
#define PTRACE_PEEKDATA PT_READ_D
#define PTRACE_KILL PT_KILL
#else // #else
#error / This system do not support.
#endif // #endif
Quiz
assert
매크로를 이용하면 인수로 주어진 표현식이 거짓일 경우 프로그램 실행을
중단시킬 수 있습니다.
하지만 이것은 실행 중에나 가능한 runtime 테스트인데요.
매크로를 활용해서 compile-time 에 테스트를 해서 빌드를 중단시키려면 어떻게 할까요?
$ gcc -xc - <<\@ && ./a.out
#define assert(expr) ((void) sizeof ((expr) ? 1 : 0), __extension__ \
({ if (expr) ; else __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); }))
#define __ASSERT_FUNCTION __extension__ __PRETTY_FUNCTION__
extern void __assert_fail (const char *__assertion, const char *__file,
unsigned int __line, const char *__function)
__attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__noreturn__));
int main() { assert(1 != 1); } // assert 매크로는 runtime 테스트 이다.
@
a.out: <stdin>:7: main: Assertion `1 != 1' failed.
-----------------------------------------------------------------------------
(void) sizeof ((expr) ? 1 : 0) 의 결과값은 ',' 연산자 왼쪽에 있기 때문에
사용되지 않는 것입니다 ( 명시적으로 (void) 로 캐스팅 했죠 ).
expr 에서 발생하는 warning 메시지 출력에 사용되고 ',' 연산자 다음식이 이어서 실행됩니다.
좀더 자세한 설명은 /usr/include/assert.h 파일을 참고하세요.
__PRETTY_FUNCTION__ 은 __func__ , __FUNCTION__ 와 같은 것인데 C++ 에서는 차이가 있습니다.
리눅스 커널 소스에 보면 BUILD_BUG_ON_ZERO(e) 매크로가 있는데요.
이 매크로를 사용하면 표현식 e
의 결과가 0
이 아닐 경우 컴파일 오류가 발생하여
빌드가 중단됩니다.
linux/include/linux/build_bug.h 참조
$ gcc -xc -fsyntax-only - <<\@
#define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
int X = BUILD_BUG_ON_ZERO(0); // 0 일 경우는 정상적으로 빌드가 진행되지만
@
OK
$ gcc -xc -fsyntax-only - <<\@
#define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
int X = BUILD_BUG_ON_ZERO(3); // 0 이 아닐 경우는 컴파일 오류가 발생하여 빌드가 중단된다.
@
<stdin>:1:51: error: negative width in bit-field ‘<anonymous>’
<stdin>:2:9: note: in expansion of macro ‘BUILD_BUG_ON_ZERO’
---------------------------------------------------------------
sizeof(struct { int: -!!(e); }))
표현식 (e) 앞에 ! (not) 이 두 번 붙으면 결과값이 무엇이든 0 아니면 1 로 되게 됩니다.
그 앞에 다시 - (minus) 가 붙었으니 최종 결과는 다음 두 가지가 될 수 있습니다.
sizeof(struct { int: 0; }) // -0 은 0 과 같으므로
sizeof(struct { int: -1; })
bit-field width 값으로 0 은 오류가 되지 않으므로 정상적으로 컴파일이 진행되지만
-1 의 경우는 컴파일 오류가 발생하여 빌드가 중단되게 됩니다.
VERIFY_OCTAL_PERMISSIONS(perms)
매크로는 BUILD_BUG_ON_ZERO(e)
매크로를 사용하여 인수로 주어진 permission 값이 특정 조건을
만족하는지 체크합니다.
BUILD_BUG_ON_ZERO(e)
매크로에서 오류가 발생하지 않을 경우 확장 값은 0
과 같게되므로
모든 테스트가 통과하면 VERIFY_OCTAL_PERMISSIONS(perms)
매크로의 최종 확장 값은
전달된 인수 그대로 (perms)
이 되게 됩니다.
linux/include/linux/kernel.h 참조
$ gcc -xc -fsyntax-only - <<\@
#define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
#define VERIFY_OCTAL_PERMISSIONS(perms) \
(BUILD_BUG_ON_ZERO((perms) < 0) + \
BUILD_BUG_ON_ZERO((perms) > 0777) + \
/* USER_READABLE >= GROUP_READABLE >= OTHER_READABLE */ \
BUILD_BUG_ON_ZERO((((perms) >> 6) & 4) < (((perms) >> 3) & 4)) + \
BUILD_BUG_ON_ZERO((((perms) >> 3) & 4) < ((perms) & 4)) + \
/* USER_WRITABLE >= GROUP_WRITABLE */ \
BUILD_BUG_ON_ZERO((((perms) >> 6) & 2) < (((perms) >> 3) & 2)) + \
/* OTHER_WRITABLE? Generally considered a bad idea. */ \
BUILD_BUG_ON_ZERO((perms) & 2) + \
(perms))
int X = VERIFY_OCTAL_PERMISSIONS(0666); // 0666 permission 은 안된다.
@
<stdin>:1:51: error: negative width in bit-field ‘<anonymous>’
<stdin>:12:3: note: in expansion of macro ‘BUILD_BUG_ON_ZERO’
<stdin>:15:9: note: in expansion of macro ‘VERIFY_OCTAL_PERMISSIONS’
----------------------------------------------------------------------
$ gcc -xc -fsyntax-only - <<\@
#define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
#define VERIFY_OCTAL_PERMISSIONS(perms) \
(BUILD_BUG_ON_ZERO((perms) < 0) + \
BUILD_BUG_ON_ZERO((perms) > 0777) + \
/* USER_READABLE >= GROUP_READABLE >= OTHER_READABLE */ \
BUILD_BUG_ON_ZERO((((perms) >> 6) & 4) < (((perms) >> 3) & 4)) + \
BUILD_BUG_ON_ZERO((((perms) >> 3) & 4) < ((perms) & 4)) + \
/* USER_WRITABLE >= GROUP_WRITABLE */ \
BUILD_BUG_ON_ZERO((((perms) >> 6) & 2) < (((perms) >> 3) & 2)) + \
/* OTHER_WRITABLE? Generally considered a bad idea. */ \
BUILD_BUG_ON_ZERO((perms) & 2) + \
(perms))
int X = VERIFY_OCTAL_PERMISSIONS(0644); // 0644 는 OK
@
OK
문c 블로그 참조 http://jake.dothome.co.kr/device-driver-1/
linux/include/linux/sysfs.h 참조
#define __ATTR(_name, _mode, _show, _store) { \
.attr = {.name = __stringify(_name), \
.mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
.show = _show, \
.store = _store, \
}
__ATTR() 매크로에서 사용하는 VERIFY_OCTAL_PERMISSIONS() 매크로는
컴파일 타임에 파일 권한 값이 다음의 조건을 만족하는지 체크한다.
- 0000 ~ 0777 범위 이내
- USER_READABLE >= GROUP_READABLE >= OTHER_READABLE
* 0444 (ok)
* 0440 (ok)
* 0400 (ok)
* 0000 (ok)
* 0404 (X)
* 0004 (X)
* 0040 (X)
* 가장 많이 실수하는 값은 예전에는 허용했던 0666 값이 최근에는
OTHER_WRITABLE 때문에 문제가 발생한다. 이를 0644 등으로 수정해야 한다.
- USER_WRITABLE >= GROUP_WRITABLE
* 0220 (ok)
* 0200 (ok)
* 0000 (ok)
* 0020 (X)
* OTHER_WRITABLE 안됨!
* 0XX0 (ok)
* 0XX2 (X)
compiletime assert 방법에는 gcc 의 error function attribute 를 사용하는 방법도 있습니다. 이것도 리눅스 커널에서 사용되고 있는 방법인데, 오류와 관련해서 메시지를 설정할 수 있습니다. 이 방법은 확장 결과가 statement 가 되므로 대입 연산에서는 사용할 수 없습니다.
$ gcc -c -xc - <<\@
#define compiletime_assert(condition, msg) \
do { \
extern void _compiletime_assert(void) __attribute__(( error( #msg ))); \
if (!(condition)) \
_compiletime_assert(); \
} while (0)
int main() { compiletime_assert(2 < 1, "2 is definitely bigger than 1"); }
@
<stdin>: In function ‘main’:
<stdin>:5:13: error: call to ‘_compiletime_assert’ declared with attribute error:
"2 is definitely bigger than 1"
<stdin>:8:14: note: in expansion of macro ‘compiletime_assert’
대입 연산에서 사용하려면 다음과 같이 statement expressions 을 이용하면 되는데 이 방법은 enum 값이나 bit-field width 같은 상수가 사용되는 곳에서는 사용할 수 없고 함수 밖에서도 사용할 수 없습니다.
$ gcc -c -xc - <<\@
#define compiletime_assert(condition, msg) \
({ \
extern void _compiletime_assert(void) __attribute__(( error( #msg ))); \
if (!(condition)) \
_compiletime_assert(); \
(condition); \
})
int main() { int X = compiletime_assert(2 < 1, "2 is definitely bigger than 1"); }
@
<stdin>: In function ‘main’:
<stdin>:5:13: error: call to ‘_compiletime_assert’ declared with attribute error:
"2 is definitely bigger than 1"
<stdin>:8:22: note: in expansion of macro ‘compiletime_assert’