C의 프리프로세서
프리프로세서(이하 전처리기)란, C 프로그램이 컴파일되기 이전에 텍스트 프로세싱을 하는 방법을 말합니다.
여기서 텍스트 프로세싱이란 헤더 파일을 포함하거나 매크로 상수 및 함수를 만드는 등의 행위를 뜻합니다.
모든 C 프로그램은 실제로 컴파일되기 이전 전처리 과정을 거칩니다.
전처리 과정은 프로그램을 훑어보고 '프로세서 지시문'이라고 하는 컴파일러가 이해 가능한 명령어를 찾아냅니다.
모든 프로세서 지시문은 #(해시)
기호로 시작합니다. 또한, C++ 컴파일러도 C와 같은 전처리기를 사용합니다.
전처리는 컴파일러가 보기 전에 코드에 예비 연산(조건부 컴파일 코드, 파일 등)을 수행하는 컴파일 과정의 일부입니다.
이러한 과정은 전처리기의 출력이 여전히 텍스트라는 것을 의미합니다.
참고사항: 기술적으로 C의 전처리 단계 출력은 소스 텍스트가 아닌 토큰 시퀀스 구조로 구성됩니다. |
지시문
+/-지시문은 소스 코드의 일부 또는 전부를 처리하거나 최종 개체에 일부 플래그를 설정하는 방법에 대한 전처리기 또는 컴파일러에 대한 특별 지시사항입니다.
이러한 지시문은 코드 작성을 더 쉽고 보기 좋게 만들 수 있습니다.
이해할 수 있는 명령어는 전처리기에 의해 처리되는데, 여기서 전처리기는 컴파일러에 의해 호출되거나 컴파일러에 의해 호출되는 별도의 프로그램입니다.
#include
+/-C는 모든 표준 호환 C 컴파일러와 함께 사용할 수 있는 코드의 저장소인 표준 라이브러리의 일부로서 여러 기능들을 가지고 있습니다.
그리고 C 컴파일러는 당신의 프로그램을 컴파일할 때, 일반적으로 표준 C 라이브러리와 연결시킵니다.
예를 들어, #include <stdio.h>
지시문을 실제 <stdio.h>
헤더 파일의 내용으로 바꿔줍니다.
라이브러리에서 기능을 사용할 때, C는 당신이 그 기능을 사용하겠다고 '선언(Declaration)'하는 것을 권장합니다.
따라서 프로그램의 첫 번째 줄은 보통 전처리 지시문으로, 다음과 같이 표시되어야 합니다.
#include <stdio.h>
위 코드는 stdio.h
에 있는 C 선언을 포함합니다.
다시 말해 stdio.h
라고 불리는 헤더 파일의 내용을 당신의 프로그램에 삽입함으로써 구현된다고 할 수 있으며, 이 경우 시스템의 종속 위치에 있다고 합니다.
이러한 헤더 파일의 위치는 컴파일러의 설명서에 기재되어 있고 표준 C 헤더 파일 목록은 헤더 표에 나와 있습니다.
stdio.h
헤더는 스트림(Stream)이라고 불리는 입출력 메커니즘의 추상화를 사용하여 입출력(I/O)을 위한 다양한 선언을 포함합니다.
예를 들어 텍스트를 표준 출력으로 출력하는 데 사용되는 stdout
이라는 출력 스트림 개체가 있는데, 이는 일반적으로 텍스트를 컴퓨터 화면에 표시합니다.
위의 예와 같은 화살 괄호(< >
)를 사용하는 경우, 전처리기는 표준 라이브러리 포함 설정에 따른 환경 경로를 자동으로 찾아가 포함할 파일을 검색합니다.
#include "other.h"
위 코드와 같이 따옴표(" "
)를 사용하는 경우, 전처리기는 사용자 정의 위치에서 검색하며 해당 위치에 없는 경우에만 표준 라이브러리 포함 경로를 찾아봅니다.
일반적으로 이러한 따옴표 기재 방식은 #include
지시문을 사용하는 파일과 동일한 디렉터리에서의 검색을 뜻합니다.
참고사항: |
Headers
+/-C90의 표준 헤더 목록
C90 이후 추가된 헤더 목록
#pragma
+/-pragma(실용적인 정보) 지시문은 표준의 일부이지만, 사용되는 표준의 소프트웨어 구현에 따라 의미가 달라집니다.
#pragma
지시문은 컴파일러로부터 특별한 동작을 요청하는 방법을 제공합니다.
또한 이 명령은 비정상적으로 코드가 크거나 특정 컴파일러의 기능을 활용해야 하는 프로그램에 가장 유용합니다.
pragma는 소스 프로그램 내에서 사용됩니다.
#pragma (토큰)
#pragma
는 일반적으로 컴파일러가 따라야 할 명령을 나타내는 단일 토큰이 뒤따르며, 지원되는 토큰 목록은 사용할 C 표준의 소프트웨어 구현을 확인해야 합니다.
참고로 #pragma
지시문에 나타나는 명령어 집합은 컴파일러마다 다릅니다.
따라서 컴파일러가 허용하는 명령어와 기능을 확인하려면 해당 컴파일러의 설명서를 참조해야 합니다.
예를 들어, 가장 많이 사용되는 전처리 지시문 중 하나인 #pragma once
는 헤더 파일의 시작 부분에 위치해야 하며 전처리기 과정에서 여러 번 포함될 경우 해당 파일이 건너뛰어진다는 것을 나타냅니다.
참고사항: 일반적으로 include guards기능을 사용하여 |
#define
+/-
경고: 전처리기 매크로는 매력적인 기능이지만 제대로 처리되지 않을 경우 예상치 못한 결과를 초래할 수 있습니다. |
#define
지시문은 프로그램 소스 코드를 컴파일하기 전에 전처리기에 의해 상수나 매크로 함수를 정의하기 위해 사용됩니다.
전처리기의 정의들은 컴파일러가 소스 코드를 보기 전에 대체되기 때문에, #define
에 의해 생겨난 오류들은 추적하기 어렵습니다.
관례상 #define
를 사용하여 정의된 값은 모두 대문자로 씁니다.
그렇게 하는 것이 필수는 아니지만, 그렇게 하지 않는 것은 매우 나쁜 관행으로 여겨집니다.
모두 대문자여야 소스 코드를 읽을 때 매크로임을 쉽게 식별할 수 있기 때문입니다.
요즘엔 #define
이 주로 컴파일러와 플랫폼 차이를 다루는 데 사용됩니다.
예를 들어 시스템 호출에 적합한 (상수로 된) 오류 값의 용도로 사용할 수 있습니다.
따라서 #define
의 사용은 정말로 필요하지 않는 한 사용하지 말아야 합니다.
typedef
문과 상수 선언문은 보통 동일한 기능을 더 안전하게 수행할 수 있습니다.
#define
명령어의 또 다른 특징은 매개변수를 받을 수 있다는 것입니다.
다음의 코드를 참고하십시오.
#define ABSOLUTE_VALUE(x) ( ((x) < 0) ? -(x) : (x) )
...
int x = -1;
while(ABSOLUTE_VALUE(x)) {
...
}
복잡한 매크로를 사용할 때는 보통 추가적인 괄호를 사용하는 것이 좋습니다.
위의 예제에서 변수 x
는 항상 괄호 안에 있습니다.
이 방법을 사용하면 0과 비교하거나 -1을 곱하기 전에 x
의 값이 평가됩니다.
또한 전체 매크로가 다른 코드에 의해 오염되는 것을 방지하기 위해 괄호로 둘러싸여 있습니다.
이는 괄호를 사용하지 않는 경우 컴파일러가 코드를 잘못 해석할 위험이 있기 때문입니다.
이러한 부작용 때문에 위에서 설명한 것처럼, 매크로 기능을 사용하는 것은 매우 나쁜 생각으로 여겨집니다.
int x = -10;
int y = ABSOLUTE_VALUE(x++);
만약 ABLUTLE_VALUE()
가 (매크로가 아닌)진짜 함수였다면, x
의 값은 -9가 되겠지만, 매크로의 매개변수로 들어갔기 때문에 두 번 평가되어(x++
가 (x)
로 들어가기 때문에) 값은 -8이 됩니다.
예제: #define MAX(a, b) a > b ? a : b
그리고 그 매크로를 이렇게 사용했다고 합시다. i = MAX(2, 3) + 5;
j = MAX(3, 2) + 5;
이 경우 컴파일러가 보게 될 코드는 이렇게 대체될 것입니다. int i = 2 > 3 ? 2 : 3 + 5;
int j = 3 > 2 ? 3 : 2 + 5;
따라서 실행 후 예상 결과는 당신은 이제 #define MAX(a, b) ((a) > (b) ? (a) : (b))
이렇게만 사용한다면 확실히 부작용은 없어 보일 수 있습니다. i = 2;
j = 3;
k = MAX(i++, j++);
하지만 이렇게 사용한다면, 결과는 inline int max(int a, int b) {
return a > b ? a : b;
}
이 경우 위의 매크로처럼 이상하게 작동하지는 않지만, 모든 타입에서 작동하지 않는다는 단점이 있기는 합니다.
|
(#
, ##
)
#
및 ##
연산자는 #define
매크로와 함께 사용됩니다.
#
을 사용하면 #
뒤에 있는 첫 번째 매개변수가 큰따옴표로 감싸진 것처럼 문자열로 반환됩니다.
예제 코드를 보자면
#define as_string(s) # s
이 매크로의 경우 매크로 프로세서는
puts(as_string(Hello World!));
이 코드를
puts("Hello World!");
이렇게 변환시킬 것입니다.
##
을 사용하면 ##
앞에 있는 매개변수와 뒤에 있는 매개변수를 연결할 수 있습니다.
예제 코드를 보자면
#define concatenate(x, y) x ## y
...
int xy = 10;
...
이 매크로의 경우 매크로 프로세서는
printf("%d", concatenate(x, y));
이 코드를
printf("%d", xy);
이렇게 변환시킬 것입니다.
결과적으로 10을 출력합니다.
또한 매크로 인수를 일정한 접두사 또는 접미사처럼 사용하여 다음과 같이 유용하게 사용할 수 있습니다.
#define make_function(name) int my_ ## name (int foo) {}
make_function(bar)
결과는 my_bar()
함수가 될 것입니다.
하지만 연결 연산자를 사용해서 매크로 매개변수를 하나의 상수 문자열로 통합하는 것은 불가능합니다.
만약 그렇게 하고 싶다면 ANSIC 속성을 사용해 두 개 이상의 연속 문자열을 단일 문자열과 동일한 것으로 간주할 수 있습니다.
상수 문자열로 통합할 수 없는 것의 예제는 이렇습니다.
#define eat(what) puts("I'm eating " #what " today.")
eat( fruit );
이러한 코드를 매크로 프로세서는
puts( "I'm eating " "fruit" " today." );
이렇게 C 파서에 의해 단일 문자열 여러 개로 변환시킬 것입니다.
다음 방법은 숫자 상수를 문자열 리터럴로 변환하는 데 사용할 수 있습니다.
#define num2str(x) str(x)
#define str(x) #x
#define CONST 23
puts(num2str(CONST));
이 경우 2단계로 확장되기 때문에 조금 까다롭습니다.
첫 번째 num2str(CONST)
는 str(23)
로 대체되며, 다시 23
으로 대체됩니다.
이러한 기능은 다음 예제에 유용하게 사용될 수 있습니다.
#ifdef DEBUG
#define debug(msg) fputs(__FILE__ ":" num2str(__LINE__) " - " msg, stderr)
#else
#define debug(msg)
#endif
이렇게 하면 파일 이름과 에러 줄 위치가 포함된 유용한 디버그 메시지를 표시할 수 있습니다.
DEBUG
컴파일 매개변수가 정의되지 않으면 디버깅 메시지가 코드에서 완전히 사라집니다.
이는 유용하지만 컴파일 매개변수 설정에 따라 나타났다가 사라지는 버그가 발생할 수 있으므로, 부작용이 우려되면 사용하지 않도록 주의하십시오.
매크로
+/-매크로는 컴파일러에 의해 검사되지 않으므로 매개변수를 평가하지 않습니다.
또한 코드의 구역을 제대로 따르지 않고 단순히 전달된 문자열을 가져와서 각 매크로 매개변수에 해당하는 실제 문자열로 대체할 뿐입니다.(코드는 문자 그대로 호출된 위치에 복사 & 붙여넣기 됩니다).
매크로의 작동 방식 예제를 봅시다.
#include <stdio.h>
#define SLICES 8
#define ADD(x) ((x) / SLICES)
int main(void)
{
int a = 0, b = 10, c = 6;
a = ADD(b + c);
printf("%d\n", a);
return 0;
}
이 경우 결과적으로 x
의 값으로 b + c
가 들어갔으나 (x)
처럼 괄호를 사용했으므로 정상적으로 a
는 2가 됩니다.
참고사항: 일반적으로 머리글에 매크로를 정의하는 것은 좋지 않습니다. |
인라인 함수가 작동하지 않는 몇 안 되는 상황 중 하나는 컴파일 과정에서 사용해야 할 상수(정적 구조체 등)를 초기화해야 하는 경우입니다.
이것은 매크로에 대한 매개변수가 컴파일러의 다른 값에 의해 최적화될 수 있는 값일 경우 발생합니다.
[1]
#error
+/-#error
명령은 컴파일을 중지합니다.
표준에 명시된 에러가 났음을 나타내는 어떠한 토큰이 발견될 경우, 이는 컴파일러가 명령어의 남은 토큰을 포함하는 메시지를 출력해야 한다고 알려줍니다.
따라서 #error
는 대부분 디버깅 목적으로 사용됩니다.
프로그래머는 조건부 블록 안에서 #error를 사용하여 블록의 시작 부분에서 #if 또는 #ifdef가 컴파일 과정에서 문제를 감지하면 컴파일러를 즉시 중지하도록 만듭니다.
일반적으로 컴파일러는 블록(및 블록 내부의 #error 지시)을 건너뛰고 컴파일을 진행합니다.
#error message
#warning
+/-많은 컴파일러가 #warning
명령을 지원합니다.
경고를 일으키는 어떠한 토큰이 발견되면, 컴파일러는 지시문의 나머지 토큰을 포함하는 메시지를 출력합니다.
#warning message
#undef
+/-#undef
지시문은 매크로를 따로 정의하지 않습니다.
따라서 식별자를 미리 정의할 필요는 없습니다.
#if,#else,#elif,#endif (조건부 지시문)
+/-#if
지시문은 해당 조건식이 0으로 평가되는지 또는 0이 아닌지를 확인하고 각각 코드 블록을 제외하거나 포함합니다.
예제 코드를 봅시다.
#if 1
/* 이 구역은 포함됩니다. */
#endif
#if 0
/* 이 구역은 포함되지 않습니다. */
#endif
여기서 조건식에는 할당 연산자와 증감 연산자, 주소 연산자, sizeof()
연산자를 제외한 모든 C 연산자를 사용할 수 있습니다.
전처리에 사용되는 유일한 연산자는 defined
연산자입니다.
즉, defined(macro)
로 묶인 매크로가 현재 정의되어 있으면 1을 반환하고, 정의되어 있지 않으면 0을 반환합니다.
#endif
지시문은 #if
, #ifdef
또는 #ifndef
로 시작하는 블록을 종료하는 데 쓰입니다.
또한 #elif
지시문은 코드 블록을 한 번 더 나눌 수 있다는 점에서 #if
와 유사합니다.
예제 코드를 봅시다.
#if /* 조건문 */
:
:
:
#elif /* 또 다른 조건문 */
:
/* 추가로 #elif를 얼마든지 넣을 수 있습니다. */
:
#else
/* #else는 #if을 만족하지 않는 경우에 관한 처리를 할 때 또는
#elif 지시문을 사용했을 때 사용합니다. */
:
:
#endif /* 여기가 #if의 끝입니다. */
#ifdef,#ifndef
+/-The #ifdef command is similar to #if
, except that the code block following it is selected if a macro name is defined. In this respect,
#ifdef NAME
is equivalent to
#if defined NAME
The #ifndef command is similar to #ifdef, except that the test is reversed:
#ifndef NAME
is equivalent to
#if !defined NAME
#line
+/-This preprocessor directive is used to set the file name and the line number of the line following the directive to new values. This is used to set the __FILE__ and __LINE__ macros.
Useful Preprocessor Macros for Debugging
+/-ANSI C defines some useful preprocessor macros and variables,[2][3] also called "magic constants", include:
__FILE__ => The name of the current file, as a string literal
__LINE__ => Current line of the source file, as a numeric literal
__DATE__ => Current system date, as a string
__TIME__ => Current system time, as a string
__TIMESTAMP__ => Date and time (non-standard)
__cplusplus => undefined when your C code is being compiled by a C compiler; 199711L when your C code is being compiled by a C++ compiler compliant with 1998 C++ standard.
__func__ => Current function name of the source file, as a string (part of C99)
__PRETTY_FUNCTION__ => "decorated" Current function name of the source file, as a string (in GCC; non-standard)
Compile-time assertions
+/-Compile-time assertions can help you debug faster than using only run-time assert() statements, because the compile-time assertions are all tested at compile time, while it is possible that a test run of a program may fail to exercise some run-time assert() statements.
Prior to the C11 standard, some people[4][5][6] defined a preprocessor macro to allow compile-time assertions, something like:
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
The static_assert.hpp
Boost library defines a similar macro.[7]
Since C11, such macros are obsolete, as _Static_assert
and its macro equivalent static_assert
are standardized and built-in to the language.
X-Macros
+/-One little-known usage pattern of the C preprocessor is known as "X-Macros".[8][9][10][11] An X-Macro is a header file or macro. Commonly these use the extension ".def" instead of the traditional ".h". This file contains a list of similar macro calls, which can be referred to as "component macros". The include file is then referenced repeatedly in the following pattern. Here, the include file is "xmacro.def" and it contains a list of component macros of the style "foo(x, y, z)".
#define foo(x, y, z) doSomethingWith(x, y, z);
#include "xmacro.def"
#undef foo
#define foo(x, y, z) doSomethingElseWith(x, y, z);
#include "xmacro.def"
#undef foo
(etc...)
The most common usage of X-Macros is to establish a list of C objects and then automatically generate code for each of them. Some implementations also perform any #undef
s they need inside the X-Macro, as opposed to expecting the caller to undefine them.
Common sets of objects are a set of global configuration settings, a set of members of a struct, a list of possible XML tags for converting an XML file to a quickly-traversable tree, or the body of an enum declaration; other lists are possible.
Once the X-Macro has been processed to create the list of objects, the component macros can be redefined to generate, for instance, accessor and/or mutator functions. Structure serializing and deserializing are also commonly done.
Here is an example of an X-Macro that establishes a struct and automatically creates serialize/deserialize functions. For simplicity, this example doesn't account for endianness or buffer overflows.
File star.def:
EXPAND_EXPAND_STAR_MEMBER(x, int)
EXPAND_EXPAND_STAR_MEMBER(y, int)
EXPAND_EXPAND_STAR_MEMBER(z, int)
EXPAND_EXPAND_STAR_MEMBER(radius, double)
#undef EXPAND_EXPAND_STAR_MEMBER
File star_table.c:
typedef struct {
#define EXPAND_EXPAND_STAR_MEMBER(member, type) type member;
#include "star.def"
} starStruct;
void serialize_star(const starStruct *const star, unsigned char *buffer) {
#define EXPAND_EXPAND_STAR_MEMBER(member, type) \
memcpy(buffer, &(star->member), sizeof(star->member)); \
buffer += sizeof(star->member);
#include "star.def"
}
void deserialize_star(starStruct *const star, const unsigned char *buffer) {
#define EXPAND_EXPAND_STAR_MEMBER(member, type) \
memcpy(&(star->member), buffer, sizeof(star->member)); \
buffer += sizeof(star->member);
#include "star.def"
}
Handlers for individual data types may be created and accessed using token concatenation ("##
") and quoting ("#
") operators.
For example, the following might be added to the above code:
#define print_int(val) printf("%d", val)
#define print_double(val) printf("%g", val)
void print_star(const starStruct *const star) {
/* print_##type will be replaced with print_int or print_double */
#define EXPAND_EXPAND_STAR_MEMBER(member, type) \
printf("%s: ", #member); \
print_##type(star->member); \
printf("\n");
#include "star.def"
}
Note that in this example you can also avoid the creation of separate handler functions for each datatype in this example by defining the print format for each supported type, with the additional benefit of reducing the expansion code produced by this header file:
#define FORMAT_(type) FORMAT_##type
#define FORMAT_int "%d"
#define FORMAT_double "%g"
void print_star(const starStruct *const star) {
/* FORMAT_(type) will be replaced with FORMAT_int or FORMAT_double */
#define EXPAND_EXPAND_STAR_MEMBER(member, type) \
printf("%s: " FORMAT_(type) "\n", #member, star->member);
#include "star.def"
}
The creation of a separate header file can be avoided by creating a single macro containing what would be the contents of the file. For instance, the above file "star.def" could be replaced with this macro at the beginning of:
File star_table.c:
#define EXPAND_STAR \
EXPAND_STAR_MEMBER(x, int) \
EXPAND_STAR_MEMBER(y, int) \
EXPAND_STAR_MEMBER(z, int) \
EXPAND_STAR_MEMBER(radius, double)
and then all calls to #include "star.def"
could be replaced with a simple EXPAND_STAR
statement. The rest of the above file would become:
typedef struct {
#define EXPAND_STAR_MEMBER(member, type) type member;
EXPAND_STAR
#undef EXPAND_STAR_MEMBER
} starStruct;
void serialize_star(const starStruct *const star, unsigned char *buffer) {
#define EXPAND_STAR_MEMBER(member, type) \
memcpy(buffer, &(star->member), sizeof(star->member)); \
buffer += sizeof(star->member);
EXPAND_STAR
#undef EXPAND_STAR_MEMBER
}
void deserialize_star(starStruct *const star, const unsigned char *buffer) {
#define EXPAND_STAR_MEMBER(member, type) \
memcpy(&(star->member), buffer, sizeof(star->member)); \
buffer += sizeof(star->member);
EXPAND_STAR
#undef EXPAND_STAR_MEMBER
}
and the print handler could be added as well as:
#define print_int(val) printf("%d", val)
#define print_double(val) printf("%g", val)
void print_star(const starStruct *const star) {
/* print_##type will be replaced with print_int or print_double */
#define EXPAND_STAR_MEMBER(member, type) \
printf("%s: ", #member); \
print_##type(star->member); \
printf("\n");
EXPAND_STAR
#undef EXPAND_STAR_MEMBER
}
or as:
#define FORMAT_(type) FORMAT_##type
#define FORMAT_int "%d"
#define FORMAT_double "%g"
void print_star(const starStruct *const star) {
/* FORMAT_(type) will be replaced with FORMAT_int or FORMAT_double */
#define EXPAND_STAR_MEMBER(member, type) \
printf("%s: " FORMAT_(type) "\n", #member, star->member);
EXPAND_STAR
#undef EXPAND_STAR_MEMBER
}
A variant which avoids needing to know the members of any expanded sub-macros is to accept the operators as an argument to the list macro:
File star_table.c:
/*
Generic
*/
#define STRUCT_MEMBER(member, type, dummy) type member;
#define SERIALIZE_MEMBER(member, type, obj, buffer) \
memcpy(buffer, &(obj->member), sizeof(obj->member)); \
buffer += sizeof(obj->member);
#define DESERIALIZE_MEMBER(member, type, obj, buffer) \
memcpy(&(obj->member), buffer, sizeof(obj->member)); \
buffer += sizeof(obj->member);
#define FORMAT_(type) FORMAT_##type
#define FORMAT_int "%d"
#define FORMAT_double "%g"
/* FORMAT_(type) will be replaced with FORMAT_int or FORMAT_double */
#define PRINT_MEMBER(member, type, obj) \
printf("%s: " FORMAT_(type) "\n", #member, obj->member);
/*
starStruct
*/
#define EXPAND_STAR(_, ...) \
_(x, int, __VA_ARGS__) \
_(y, int, __VA_ARGS__) \
_(z, int, __VA_ARGS__) \
_(radius, double, __VA_ARGS__)
typedef struct {
EXPAND_STAR(STRUCT_MEMBER, )
} starStruct;
void serialize_star(const starStruct *const star, unsigned char *buffer) {
EXPAND_STAR(SERIALIZE_MEMBER, star, buffer)
}
void deserialize_star(starStruct *const star, const unsigned char *buffer) {
EXPAND_STAR(DESERIALIZE_MEMBER, star, buffer)
}
void print_star(const starStruct *const star) {
EXPAND_STAR(PRINT_MEMBER, star)
}
This approach can be dangerous in that the entire macro set is always interpreted as if it was on a single source line, which could encounter compiler limits with complex component macros and/or long member lists.
This technique was reported by Lars Wirzenius[12] in a web page dated January 17, 2000, in which he gives credit to Kenneth Oksanen for "refining and developing" the technique prior to 1997. The other references describe it as a method from at least a decade before the turn of the century.
We discuss X-Macros more in a later section, Serialization and X-Macros.
- ↑ David Hart, Jon Reid. "9 Code Smells of Preprocessor Use". 2012.
- ↑ HP C Compiler Reference Manual
- ↑ C++ reference: Predefined preprocessor variables
- ↑ "Compile Time Assertions in C" by Jon Jagger 1999
- ↑ Pádraig Brady. "static assertion".
- ↑ "ternary operator with a constant (true) value?".
- ↑ Wikipedia: C++0x#Static assertions
- ↑ Wirzenius, Lars. C Preprocessor Trick For Implementing Similar Data Types Retrieved January 9, 2011.
- ↑ 틀:Cite journal
- ↑ 틀:Cite journal
- ↑ Keith Schwarz. "Advanced Preprocessor Techniques". 2009. Includes "Practical Applications of the Preprocessor II: The X Macro Trick".
- ↑ Wirzenius, Lars. C Preprocessor Trick For Implementing Similar Data Types Retrieved January 9, 2011.