8.2 マクロ定数
8.2.1 トークン列の展開
以前 const 型修飾子を用いて変更不可能な変数を作成する手法を解説しました。これは、開発者にプログラムの中で常に特定の定数を示す識別子を与えました。しかし、変数は作成するだけでもメモリコストという要因があり、さらにポインタによる間接参照であれば、CPUのアドレス計算が発生してしまいます。多くの C 言語プログラマはこうした無駄をなくし、より高速でスマートなプログラムを作りたいと考えているため、これは大きな問題です。
そこで考えられるのが、プリプロセッサです。プリプロセッサはコンパイル前にソースを整形する能力を持つため、プリプロセッサでできることはプリプロセッサに行わせることによって、処理の効率化を図ることができます。
プリプロセッサ命令には、トークン列を展開する #define ディレクティブが存在します。 #define ディレクティブで定義される識別子をマクロと呼び、多くの C 言語プログラマがこの機能を多用しています。
#define 識別子 トークン列
トークン列に指定する内容はどのようなものでもかまいません。プログラム中に #define ディレクティブで作成した識別子を指定すると、その位置にトークン列がそのまま展開されます。トークン列が展開される時は、前後の空白がカットされています。#define ディレクティブで作成したトークン列を表す識別子は、#define ディレクティブ以下のあらゆる場所に指定することができます。極端なことをいえば、次のプログラムも正当です。
#define MAIN int main() { return 0; } MAIN
これは #define ディレクティブを用いて int main() { return 0; } というトークン列を表す MAIN という識別子を作成しています。このように、#define ディレクティブはトークン列に名前を付けることができるのです。あとは、その後のプログラム中の必要な場所でトークン列の名前を指定すれば、コンパイル時にプリプロセッサがトークン列に置き換えてくれるのです。上記した2行は、コンパイル時には int main() { return 0; } という文字列に置き換えられています。
この機能をうまく利用すれば、変数を使わずに整合性のある定数処理を行うことができます。実際に、C言語プログラマは定数を用いる場合 const 型修飾子や enum を使うことよりも #define ディレクティブによるプリプロセッサ時の置き換えを採用する傾向があります。関数に渡す論理的な意味のある属性値や、関数が返すエラーコードなどは #define ディレクティブの識別子で区別する手法が一般的なのです。
#include <stdio.h> #define KITTY "Kitty on your lap" #define BUFFER 0xFF int main() { char str[BUFFER] = KITTY; printf("%s\n" , str); return 0; }
コード1はリテラル文字列を表す KITTY と、0xFF という整定数を表す BUF という名前を定義しています。#define ディレクティブで定義したトークン列の名前は、プログラム中のあらゆる場所で指定することができます。これらの名前はコンパイル前にプリプロセッサが置き換えるため、C言語の構文には関与しないのです。もちろん、展開後のトークン列が構文的に正しいかどうかは開発者の責任となります。
ヘッダファイルなどでは、こうした定数の名前付けは有効な手段であると同時に、一方では局所的な定数の名前付けという課題も生じます。プリプロセッサディレクティブには可視性という概念が存在しないため、常に #define ディレクティブで定めた名前が公開されてしまいます。const 型修飾子などであればローカル変数を使うことで局所性を実現できましたが、#define ディレクティブを使う場合はどのように解決するべきでしょうか。
こうした問題は #undef ディレクティブを用いることで解決することができます。 #undef ディレクティブは #define ディレクティブで定義した名前を削除します。これによって定義解除された名前は、それ以下の行では使うことができません。
#undef 識別子
識別子には、定義解除を保証する名前を指定します。#undef ディレクティブには必ずしも定義済みの名前を指定する必要はありません。定義されていない名前を指定しても、その識別子の未定義状態を保証するという効力があるためです。
#include <stdio.h> int main() { #define PI 3.14159265358979323846 printf("%f\n" , PI); /*OK*/ #undef PI /*printf("%f\n" , PI); /*エラー*/ return 0; }
コード2は、円周率を表す定数 PI を定義しています。main() 関数の先頭で PI を printf() 関数内で使用していますが、その後 #undef ディレクティブによって定義解除を行っています。#undef ディレクティブの行以降 PI という名前は未定義状態が保証されるため、コメント化されている printf() 関数をコンパイルすれば、エラーが発生することを確認することができるでしょう。
ヘッダファイルなどで局所的に使用する定数には、ヘッダファイルの末尾で定義解除することで名前の競合を避けることができます。