C の知識:プリプロセッサ
C のソースコードはコンパイルの際、機械語に変換される前にいくつか前処理を受けます。これを担うのがプリプロセッサ(preprocessor)です。
コードにプリプロセッサ命令(preprocessor directive、単にプリプロセッサとも)を書く事で、
- コード中の特定部分の置き換える
- コードの一部を有効/無効にする
等が可能となります。
マクロ
#defineにより作られるマクロ(macro)は置き換えを行います。
この行以降6.10.3#9のマクロ名は内容へと置換されます。
マクロは#undefにより取り消す事ができます。
引数を取らせて関数の様なものも作れます。ただしこの時、マクロは単なる「置き換え」である事に注意しなければなりません。
関数マクロの注意点
例えば次の様にして二乗を求めるマクロPOW2を定義したとして、
POW2(1+1)は2*2ではなく1+1*1+1つまり 3 となるのです。この様なバグを避ける為、関数マクロを作る場合は
としつこく括弧を付ける必要があります。
複数行のマクロ
マクロ定義では/の直後の改行が無視されるので、これを利用して複数行のマクロを作る事ができます。
例えば、事前に_tmpを宣言しておくと
により引数の値を入れ替える関数マクロSWAPが実現できそうに見えます。しかし、このマクロは次の様な使い方をされると意図しない動作を引き起こします。
これを回避する為、複数行に亘るマクロではdo { 〜 } while (0)で囲むのが定石となっています。
(単に{ 〜 }で囲むと、if (a > b) SWAP(a, b); else 〜と続く場合に問題となります。参考:「PRE10-C. 複数の文からなるマクロは do-while ループで包む」)
引数の文字列化
マクロの引数の名前の前に#を書くと、引数の内容が文字列化されます。
例えばこのマクロは引数の式と値を表示します。
のPRI_INT(a + 1);はprintf("a + 1" " = %d\n", a + 1);と展開され、実行すれば
と表示されます。
トークンの結合
##はトークンを連結します。
と定義した時、emit_err(42);は
と展開されます。
可変長引数
引数リストの最後に, ...と書くと、1 つ以上の引数を受け取って__VA_ARGS__に格納します。
例えば
によりprintfの標準エラー出力版らしきものを作れるのですが、次の様に...部分が空の呼び出しをすると
展開しても__VA_ARGS__のところに何も入らないので、
となって構文的におかしくなります。即ち...部分には 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」)
条件分岐
コード自体を条件により分岐させる事ができます。
#elifや#elseの部分は省略可能です。条件式は定数式でなければならず、defined マクロ名という式によりあるマクロが定義されているかどうかを調べられます。
例えば関数debugを
と定義すると、メッセージはDEBUGマクロが定義されている場合のみ出力されます。
このパターンはよく使われる為、次の省略した書き方も可能になっています。