[←1つ前] [→1つ後] [↑質問一覧] [↑記事一覧] [ホームページ]
C言語を強力にしている機能の一つに、前処理があります。
#include
を使ってヘッダーファイルを分離したり、
#ifdef
でマシン依存のコードを使い分けるようにして一つのプログラムを複数の環境に
対応させるといったテクニックは、多くのプログラムで使われています。今回は、
これらの前処理に関係する、よくある質問を取り上げてみます。
前処理の基本的な内容は、マクロの置き換えやインクルードファイルの読み込 み、コメントの空白化などで、その後に字句解析、コードの置き換えなどのコン パイルが行われることになります。
JISでは、前処理を次のように定義しています。「処理系は、ソースファイルの 一部分を条件によって処理したり読み飛ばしたり、他のソースファイルを組み込 んだり、マクロを置き換えたりすることができる。これらの処理は、概念的には その翻訳単位の翻訳の前に行われるので、前処理(preprocessing)と呼ぶ。」
#if (sizeof(int) == 2) ... #else ... #endif
sizeof(int)
の値が決定するのはコンパイル時ですから、
#if
の処理中にはその値を用いることができません。
このコードは
int
が2バイトか4バイトかによって振る舞いを変えたいという目的があるようですが、
それを実現するのであれば、
limits.h
をインクルードして
INT_MAX
の値で判断するという手があります。もし可能なら、
int
のサイズに依存しないようなコードを書くことがベストです。
(参考 c.l.c FAQ 10.13)
#ifdef
がたくさん現れて、実際にコンパイルした時に一体どこが有効なのか分からない
ことがある。そこで、プリプロセッサだけ通してみたら、今度は、
#include
や
#define
が展開されてしまい、余計わけが分からない。
#ifdef
だけを処理するようなことは可能か?
(参考 c.l.c FAQ 10.18)
#if 0
がたくさん出てくるコードを見た。
#if 0
と
#endif
の間のコードは無視されるはずだから、無駄ではないのか。
#if 0
と
#endif
の間のコードは、コンパイルの前処理の早い段階で読み飛ばされるので、このコ
ードがあるためにコンパイルの負荷が高くなるといったことは、まず考えなくて
も大丈夫です。
#if 0
と
#endif
の間にコメント
/*
,
*/
で区切らないコメントを書いてはいけないと言われた。
#if 0
〜と
#endif
の間も、厳密に言えば、Cのプログラムのコードとして扱われます。例えば、コ
メントを空白に置き換える処理は、
#if
の処理の前に行われます。
これに対して、「
/*
」と「
*/
」で囲んだ個所は、まさにコメント以外の何物でもありません。コメントを書く
場合には、こちらを使うのが原則です。
以前連載していた「ABC of C」のソースには、
#if 0
と
#endif
を使ってドキュメントを埋め込んだものが多数ありますが、これらは本来望まし
い書き方ではありません。ただし、C言語にはコメントがネストできないという
制約があるため、コメント中にどんなことでも書こうとすると限界があります。
特に、C言語のソースの説明をソースの中に書くという場合には、どうしようもな
い場合もあるため、邪道を承知でそのような書き方をしています。
例えば、
int
型の変数であれば、実現したいのは次のような処理となるでしょう。
#define swap(a, b) { int tmp; tmp = a; a = b; b = tmp}この処理が
int
以外の型の変数について適用できないことは自明です。逆に考えれば、交換した
い型に応じて
swap_int
、
swap_long
、
swap_double
のようなマクロ
を定義すれば、それぞれの型に応じたマクロを使うことは不可能ではありません。
ただし、このマクロ定義は不完全です。後述の 【マルチステートメント】 の項を参考にしてください。
値を交換するために必ずしもマクロを使う必然性はありません。マクロを使わ ずに実現する方が、現実的には簡単で確実なこともあります。
(参考 c.l.c FAQ 10.3)
#define swap(a, b) (a ^= b, b ^= a, a ^= b)
a
と
b
の値の中身を追跡してみると、種が簡単であることが分かります。
aの内容 bの内容 最初 a b a ^= b直後 a^b b b ^= a直後 a^b b^(a^b) = a a ^= b直後 (a^b)^a = b a排他的論理和は交換法則、結合法則が成り立つので、
b^(a^b) = b^a^b = b^b^a = 0^a = a
となるのです。
これを使えば型を気にしないマクロが書けそうですが、話はそう簡単ではあり
ません。大きな落とし穴は、一つの変数を指定した場合、つまり
swap(a, a)
のような場合です。
aの内容 bの内容 最初 a a a ^= b直後 a^a = 0 0 b ^= a直後 0 0^0 = 0 a ^= b直後 0^0 = 0 0このように、途中で値が0になってしまって、元の値が消滅してしまいます。
#define swap(a, b) (a ^= b ^= a ^= b)
現実に動作する処理系も多いと思われますが、話の種ならともかく、実用に使 うコードとしては用いない方が賢明でしょう。
(参考 c.l.c FAQ 10.3)
#define Paste(a, b) a/**/bコンパイラを新しいものにして、再コンパイルしたら、思ったように処理され なくなってしまった。
ANSI Cには、
##
という書き方追加されたので、これを使えば、やりたいことを次のようにして実
現することができます。
#define Paste(a, b) a##b
(参考 c.l.c FAQ 10.20)
'¥'
を付ければ、前処理時に一行とみなされますから、見た目に複数行に書くという
目的は達成されます。しかし、ここで問題なのは、関数呼び出しと同じように使
えるマクロが欲しいということでしょう。
例えば、
print_val(i, value)
というマクロを考えてみましょう。このマクロは、
i
が1の時には
value
が
char
型とみなして、文字そのものを、
i
が2の時には
short
、4の時は
long
とみなして、その値をプリントするものとします。残念ながら、引数の型が不定
になってしまうため、C言語の仕様では関数で書くことはできません。イメージと
しては次のリストのようになります。
print_val(i, value) { if (i == 1) printf("%c", value); else if (i == 2) printf("%d", value); else if (i == 4) printf("%ld", value); }このようなマクロを実現する古典的な方法は、
do-while(0)
を使うというものです。
#define print_val(i, value) do { ¥ if (i == 1) ¥ printf("%c", value); ¥ else if (i == 2) ¥ printf("%d", value); ¥ else if (i == 4) ¥ printf("%ld", value); ¥ } while (0)これは、次のように書いてもよさそうに見えます。
#define print_val(i, value) { ¥ if (i == 1) ¥ printf("%c", value); ¥ else if (i == 2) ¥ printf("%d", value); ¥ else if (i == 4) ¥ printf("%ld", value); ¥ }しかし、これは失敗することがあります。なぜなら、関数を呼び出す場合には、 プログラマーは殆ど無意識のうちに最後にセミコロンを書くでしょう。例えば、 次のように書くでしょう。
print_val(2, i);しかし、後者のようなマクロ定義だと、ブロックそのものが文とみなされるの で、マクロを展開した時に、最後のセミコロンが冗長になります。これが、場所 によっては邪魔になって、予期せぬ結果となることがあります。特に、
else
の付いた
if
文の後にこのようなマクロを用いると、文法エラーになってしまいます。
マクロの内容が式の羅列で書けるのであれば、このような工夫をしなくても、 コンマ演算子で区切ったマクロを使うことができます。
(参考 c.l.c FAQ 10.4)
__DATE__
や
__TIME__
のように、特に記述しなくても最初から定義されているマクロの一覧を知りたい
のだが、どうすれば知ることが出来るだろうか。
A
不思議なことに、標準的な方法は存在しません。コンパイラに付属しているマ ニュアルに一覧が書いてあるかもしれません。UNIXのstringsというツールを使っ て、コンパイラの実行ファイルから可読文字列を拾いだすというハッカー流の探 し方が現実的かもしれません。なお、ANSIの仕様として定義されているマクロは、以下のものがあります。
__LINE__ 現在のソース行の行番号。 __FILE__ ソースファイル名。 __DATE__ コンパイルした日時。 __TIME__ コンパイルした時刻。 __STDC__ 規格合致処理系であることを示す定数。1である。(参考 c.l.c FAQ 10.19)
#define DEBUG(args) (printf("DEBUG: "), printf args) if (n != 0) DEBUG(("n is %d¥n", n));この場合は、プログラマーが二重に括弧を使うことを忘れると失敗するという リスクがあります。プログラムを書く時点で引数の数が分かっているのであれば、 引数の数に応じたマクロをそれぞれ定義してしまうか、最大の引数の数に対応し たマクロを用意しておき、それより少ない引数の場合にはダミーの引数を与えて しまうという手もあります。
(参考 c.l.c FAQ 10.26)
<stdarg.h>
をインクルードして、
va_start
、
va_arg
、
va_end
マクロを使って実現するのが基本です。これらのマクロの使い方は、リファレン
スマニュアルに例が示されていると思いますので詳細は、それを参照してくださ
い。一例を示します。
/* 複数の文字列を改行して表示する。 * 最後はNULLを指定する。 * ex. printfx("一行目", "二行目", NULL); */ #include <stdio.h> #include <stdarg.h> void printfx(char *str, ...) { char *p; va_list argp; va_start(argp, str); for (p = str; p != NULL; p = va_arg(argp, char *)) printf("%s¥n", p); va_end(argp); }(参考 c.l.c. FAQ 15.4)
()
」がずいぶんたくさん付いているような気がするが、なぜそんなに括弧を付ける
必要があるのか。
#define power(a) ((a) * (a))この定義にある落とし穴は、
power(a + b);
のようなマクロの使い方をした場合を考れ分かります。マクロの処理は、単に文
字を置き換えるだけなので、括弧がないと変な個所が優先的に結合することがあ
るのです。
原則としては、引数自体に括弧を付け、マクロ全体をさらに括弧で囲むように すれば安全です。
if (isspace(c = *s++) == 0) putchar(c);
#define power(a) ((a) * (a))このマクロ
power
は、
a
を二回評価します。しかし、
power(*p++);
のようにコードを書いた時に、
*p++
が二回評価されることに気付かないかもしれません。しかも、その結果はC言語と
しては正しくないプログラムになります。なぜなら、マクロを展開した結果は次
のような未定義のコードになるからです。
((*p++) * (*p++))この危険を避けるために、プログラマーはマクロの評価回数を知った上でコー ドを書くか、あるいは、マクロの評価回数が複数でも構わないようなコードを書 くことになります。これは面倒な心構えです。特に、多くの場合マクロは関数呼 び出しと見分けが付かないことが、トラブルの原因となるでしょう。
そこで、もう一つの逃げ道として、マクロを書く時には引数の評価回数が1以下 であるようにする、ということが考えられます。
さて、実は、
isspace
マクロは、引数の評価回数が1であることが仕様により保証されていますから、
isspace(c = *s++)が、二回以上評価されることはなく、期待通りの結果が得られるはずです。
もっとも、読みやすさを重視するなら、ここは行を分けて、次のように書いた 方がよいと思います。一般論として、一度にたくさんのことをしようとすると、 それだけ分かりにくいコードになることが多いからです。
c = *s++; if (isspace(c) == 0) putchar(c);
#include
でファイルを読み込む場合に、ファイル名を
""
で囲むのと
<>
で囲むのとでは、何が違うのか。
#include <ファイル名>
の形式で指定した場合、このファイルは処理系によって定められた標準的なパス
から検索されます。通常、これは/usr/includeなどのパスになっています。また、
コンパイラのオプションやカスタマイズファイルの定義によって、パスをユーザ
ーが指定できる場合が殆どです。
#include "
ファイル名
"
による指定も同様ですが、通常、この指定の場合はカレントディレクトリを検索
対象とします。
標準ヘッダを指定する場合は
<>
、ユーザ定義ヘッダを指定する場合には
""
を使うのが一般的な用法です。
#include
することはできるか。
仕様ではなく主義の問題としては、ネスティングをすべきでないという主張と、 しても構わないという主張が、真っ向から対立しています。どちらの言い分にも 一理あり、決定的な結論はありません。
#ifndef already_defined #define already_defined ... #endifこのようにすれば、最初の読み込みで
already_defined
というマクロが定義されるため、二回目に読み込んだ時には、
#ifndef
と
#endif
の間に書かれている定義は無視されます。従って、二重定義になることはありま
せん。
(参考 c.l.c. FAQ 10.7)
#include
すればよいのかよく分からない。簡単に知る方法はないか。
#include
すればよいか書いてあります。
また、実際にヘッダーファイルを検索すれば、どこに必要な定義があるか知る ことは簡単です。grepなどのツールを使ってもよいし、エディタで開いて検索し てみてもよいでしょう。
標準ヘッダーファイルは、ある程度まとまった処理毎に分類されていますから、 それらを覚えるのは対した手間ではありません。最も典型的なものとして、以下 のようなものはすぐ覚えるはずです。
<stdio.h> 入出力 <stdlib.h> malloc、exit等の関数 <string.h> 文字列処理関数 <math.h> 数学関数もし、必要なヘッダーファイルが
#include
されていなければ、コンパイル時に関数プロトタイプが定義されていないという
警告メッセージが表示されるはずですから、それを見て何か
#include
し忘れたということに気づくはずです。
#include
するのは面倒なので、全ての標準ヘッダーを
#include
するようなall.hというヘッダーを作って、コンパイルの時に
はこれをまず
#include
することにすれば、頭を使わずに済むのでは。
ただ、最近の処理系は、資源が豊富で、かつ処理速度も速くなったため、場合 によっては全てのヘッダーを無駄なものも含めて全て読み込んでしまっても、さ ほど影響はないかもしれません。いちいちどのヘッダーを読み込むか考えなくて 済むので、かえって効率的だ、という意見の人もいます。
コンパイル時に気にならないのなら、特に実害はないでしょう。コンパイル時 間がやけに長いとか、途中でメモリ不足になって中断してしまうのであれば、考 え直した方がよいでしょう。