本物のC

C の知識:プリプロセッサ


C のソースコードはコンパイルの際、機械語に変換される前にいくつか前処理を受けます。これを担うのがプリプロセッサ(preprocessor)です。

コードにプリプロセッサ命令(preprocessor directive、単にプリプロセッサとも)を書く事で、

等が可能となります。

マクロ

#defineにより作られるマクロ(macro)は置き換えを行います。

#define マクロ名 内容

この行以降6.10.3#9のマクロ名は内容へと置換されます。

#undef マクロ名

マクロは#undefにより取り消す事ができます。

#define マクロ名(引数リスト) 内容

引数を取らせて関数の様なものも作れます。ただしこの時、マクロは単なる「置き換え」である事に注意しなければなりません。

関数マクロの注意点

例えば次の様にして二乗を求めるマクロPOW2を定義したとして、

#define POW2(x) x*x

POW2(1+1)2*2ではなく1+1*1+1つまり 3 となるのです。この様なバグを避ける為、関数マクロを作る場合は

#define POW2(x) ((x)*(x))

としつこく括弧を付ける必要があります。

複数行のマクロ

マクロ定義では/の直後の改行が無視されるので、これを利用して複数行のマクロを作る事ができます。

例えば、事前に_tmpを宣言しておくと

#define SWAP(x, y) \
_tmp = x; \
x = y; \
y = _tmp;

により引数の値を入れ替える関数マクロSWAPが実現できそうに見えます。しかし、このマクロは次の様な使い方をされると意図しない動作を引き起こします。

if (a > b)
SWAP(a, b);

これを回避する為、複数行に亘るマクロではdo { ~ } while (0)で囲むのが定石となっています。

#define SWAP(x, y) \
do { \
_tmp = x; \
x = y; \
y = _tmp; \
} while (0)

(単に{ ~ }で囲むと、if (a > b) SWAP(a, b); else ~と続く場合に問題となります。参考:「PRE10-C. 複数の文からなるマクロは do-while ループで包む」)

引数の文字列化

マクロの引数の名前の前に#を書くと、引数の内容が文字列化されます。

#define PRI_INT(x) printf(#x " = %d\n", x)

例えばこのマクロは引数の式と値を表示します。

int a = 1;
PRI_INT(a + 1);

PRI_INT(a + 1);printf("a + 1" " = %d\n", a + 1);と展開され、実行すれば

a + 1 = 2

と表示されます。

トークンの結合

##はトークンを連結します。

#define emit_err(code) fprintf(stderr, "error %d : %s\n", code, ERR_MSG_ ## code)

と定義した時、emit_err(42);

fprintf(stderr, "error %d : %s\n", 42, ERROR_MSG_42)

と展開されます。

可変長引数

引数リストの最後に, ...と書くと、1 つ以上の引数を受け取って__VA_ARGS__に格納します。

#define マクロ名(引数リスト, ...) 内容

例えば

#define debug(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)

によりprintfの標準エラー出力版らしきものを作れるのですが、次の様に...部分が空の呼び出しをすると

debug("Fatal error!\n");

展開しても__VA_ARGS__のところに何も入らないので、

fprintf(stderr, "Fatal error!\n", );

となって構文的におかしくなります。即ち...部分には 1 つ以上の引数を渡さなければならない点に注意して下さい。

(gcc 拡張では~, ## __VA_ARGS__)と書く事で、clang や msvc ではそのままでもコンパイラが気を利かせる事で__VA_ARGS__が空でも上手くいくのですが、規格に準拠した方法で対処するのはかなり技巧的になります。参考:「c - Standard alternative to GCC's ##__VA_ARGS__ trick? - Stack Overflow」)

定義済みマクロ

処理系により予め以下のマクロが用意されています。

マクロ名 内容
__DATE__ プリプロセッサにより処理された日付。"Mmm dd yyyy"の形式で、月の表現はasctime関数と同じ。
__TIME__ プリプロセッサにより処理された時刻。"hh:mm:ss"の形式を取る。
__FILE__ 現在のソースファイルの名前。
__LINE__ 現在の行番号。
__STDC__ C の規格に準拠した処理系では定数1が期待される。
__STDC_HOSTED__ ホスト環境では1で、そうなければ0の定数。
__STDC_VERSION__ 準拠している規格のバージョン。C99 では199901Lの定数。

これらは C99 で規定されています6.10.8#1。他にも環境やコンパイラによって様々な定義済みマクロがあり、例えば_WIN32は Windows 環境の識別に使えます。(参考:「定義済みマクロ (MSDN)」、「Pre-defined Compiler Macros」)

条件分岐

コード自体を条件により分岐させる事ができます。

#if 条件式1
...
#elif 条件式2
...
#else
...
#endif

#elif#elseの部分は省略可能です。条件式は定数式でなければならず、defined マクロ名という式によりあるマクロが定義されているかどうかを調べられます。

例えば関数debug

void debug(char *msg)
{
#if defined DEBUG
puts(msg);
#endif
}

と定義すると、メッセージはDEBUGマクロが定義されている場合のみ出力されます。

このパターンはよく使われる為、次の省略した書き方も可能になっています。

#ifdef マクロ名 /* #if defined マクロ名 と同じ */
#ifndef マクロ名 /* #if !(defined マクロ名) と同じ */