マクロで定数に名前を付けよう!

こんにちは、めのんです!

しばらく難しい内容が続いたので、久々に今日は簡単な内容にしようと思います。
本当に簡単になるかは私の力量次第です。
期待せずにお付き合いください。

Cのマクロ機能

Cにはマクロという機能があります。
マクロというと、もしかするとExcelなどのVBAで書いたマクロのようなものを連想するかもしれませんね。
でも、Cのマクロはそんなに高機能ではなくて、すごくシンプルなものです。

一言でいうと、Cのマクロはソースコードを単純に置き換えるだけの機能です。
これから解説する「#define」でマクロを定義するのですが、PHPのdefineに比べてもずっとシンプルです。

シンプルだからこそ強力なところもあって、Cのマクロは式に限らずどんなソースコードでも置き換えることができます。

マクロの種類

Cのマクロは大きく分けて2種類あります。
「オブジェクト形式マクロ」と「関数形式マクロ」です。
両者の違いは引数を取るかどうかです。

今回はそのうちのオブジェクト形式マクロについてだけ解説することにします。
関数形式マクロはちょっと解説することが多いので、一度には書き切れないからです。
いずれ近いうちに関数形式マクロの解説もしたいと思いますので、それまでお待ちください。

マクロの定義

オブジェクト形式マクロは次のようにして定義します。

#define number 123

上の例ではnumberという名前のマクロを定義しています。
この定義以降、numberという識別子が現れれば文脈に関係なく「123」に置き換えられます。

この「文脈に関係なく」というところが大事で、マクロには有効範囲(スコープ)も名前空間も関係ありません。

たとえば次の例をご覧ください。

#include <stdio.h>

#define begin {
#define end   }

int main(void)
begin
  puts("hello, world!");
  return 0;
end

マクロは本当に文脈に関係なくソースコードを置き換えるので、(その良し悪しは別として)上のようなこともできてしまうんです。
でも実際には上のような使い方は、よほどの事情がない限りするべきではないと思います。

こういう特殊な使い方さえしなければ、オブジェクト形式マクロは普通のオブジェクトや定数のように使うことができます。
あくまでもソースコードの置き換えですから型にも制限がありません。

マクロの再定義

同じ名前のマクロは同じ翻訳単位の中では原則として1回しか行うことができません。
翻訳単位については次回開設する予定ですけど、大まかにいうとソースファイルとそこからインクルードされるソースファイルやヘッダを合わせたものだと思ってください。

ところが特定のケースに限ればマクロを再定義することができます。
その特定のケースというのは、マクロの定義内容が同一とみなせる場合です。

同一というのは必ずしも一字一句同じである必要はありません。
Cのソースコードでは、空白類は1つの空白文字と同一とみなされます。

空白類というのは、空白文字(スペース)、水平タブ、改行、垂直タブ、書式送り、そして注釈(コメント)の総称です。
マクロの定義に空白類が含まれていれば、それが1文字だけであっても複数文字であっても同一とみなされます。

たとえばこんな感じです。

#define foo ( 1 + 2 )
#define foo ( 	1/* comment */ + 2 )  // 上と同一
#define foo (1+2)  // これは同一ではない。

ちょっとわかりにくいですが、2行目はもともと空白類があったとこの空白類の数が違うだけです。
これは同一とみなされます。

一方で、3行目はもともと空白類があったところから空白類がなくなってしまっています。
こういうのは同一ではありません。

マクロを使う上での注意点

マクロを使う上での注意点も書いておきますね。

式に展開される場合は必ず丸括弧で囲む

マクロの定義内容が式のときは、必ず丸括弧で囲むようにしましょう。

これだけ聞いてもよくわかりませんね。
具体例を挙げることにします。

#define foo 1 + 2
int value = foo * 3;

上のサンプルコードを考えてみましょう。
マクロfooは1 + 2に展開されます。
これは値が3の整数定数に展開されることを期待しているはずですよね。

もし期待通りに振る舞うのであれば、2行目のvalueは3 * 3で9になるはずです。
ところが実際には7になってしまいます。

これはどういうことかというと、実際にマクロを自分で展開してみればわかると思いますよ。

int value = 1 + 2 * 3;

上のように、valueの初期化子は= 1 + 2 * 3になります。
小学校で習ったように、かけ算は足し算より先に行われますので、1 + (2 * 3)の意味になってしまいます。
これでは期待通りに振る舞ってくれません。

そこで次のようにマクロの定義内容を丸括弧で囲んであげればうまくいくようになります。

#define foo (1 + 2)
int value = foo * 3;

この場合、valueの初期化子は次のようになります。

#define foo (1 + 2)
int value = (1+2) * 3;

今度は期待通りvalueの値が9になりました。

乱用禁止!

Cのマクロはシンプルですが非常に強力です。
下手な使い方をすれば構文を破壊してしまいます。

マクロの使用は最小限に抑え、ほかの方法がないかを探ってみましょう。

たとえば、整数定数であればマクロより列挙定数を使う方がうまくいくことの方が多いと思います。
文字列の場合も、どうしても文字列リテラルに展開されないといけない場合を除けば普通にオブジェクトして定義する方がいいでしょう。

こんな感じで危険なところもあるのですが、Cのマクロは便利なのでいろんな用途に使われています。
これまで何度も登場したNULLなんかも実はオブジェクト形式マクロです。


今回の解説は以上となります。
次回は翻訳単位とソース分割について解説したいと思います。
どうぞご期待ください!