検索
連載

プリプロセッサでプログラムの質を向上させよう目指せ! Cプログラマ(16)(2/4 ページ)

プログラミング言語Cの強力な機能の1つに、「プリプロセッサ」があります。正しく使えば、間違いが少なくて、意味も伝わりやすいプログラムを、より簡単に書くことができます。プリプロセッサを使いこなして、プログラムの質をぐっと向上させましょう。

PC用表示 関連情報
Share
Tweet
LINE
Hatena

マクロ置き換え

 マクロ置き換えの機能を使うと、プログラム中で定義したマクロを、プリプロセッサで置き換えることができます。

 例えばコンパイルするときに決まっている数(定数)を名前で定義しておくと、分かりやすいプログラムを書けます。プログラム中に「3.14」と書くよりは「VERSION」と書いた方が勘違いが少ないでしょう。

 マクロは次のように定義します。

#define VERSION 3.14

 ここでは「VERSION」がマクロ識別子で、この定義が現れた以降の「VERSION」というマクロ識別子はプリプロセッサで「3.14」に置き換えられます。これを「オブジェクト形式マクロ」と呼びます。

printf("%1.2f\n", VERSION);

 上のように記述されていれば、次のようにプリプロセッサで置き換えられてからコンパイルされます。

printf("%1.2f\n", 3.14);

 マクロを使った場合には、プログラマの意図が格段に明確になります。さらに、VERSIONというマクロが複数の個所で使われるとき、「#define」のマクロ定義の値だけを修正すれば、すべてをまとめて変えられるというメリットがあります。

 マクロ置き換えでは、単なる定数だけではなく、下のようにもっと複雑な置き換えもできます。

#define MAX(a, b) ((a) > (b) ? (a) : (b))

 上のようなマクロ定義(関数形式マクロ)があると、それ以降のプログラム中で

MAX(1, 2)

((1) > (2) ? (1) : (2))

に置き換えられます。マクロMAXは関数と似たような形で引数を取り、その識別子の要素が置き換えられます。

 このようにマクロ置き換えは便利な機能なので、実際のプログラムではよく使われます。何度も繰り返される簡単な処理が、関数にするほどでもなかったり、関数に置き換えられないような処理だったときでも、マクロで置き換えられることがあります。

マクロの無効化

 マクロは一度定義すると、その位置からソースコードの最後まで有効になります。同じ名前のマクロを2回定義しようとするとエラーになります。限られた範囲でしか使わないマクロは「#undef」を使って無効にできます。

#define VERSION 1
// ここでは 1 の VERSION マクロが有効
#undef VERSION
// 1 の VERSION マクロが無効
// 1 の VERSION マクロが無効になったので、同じ名前のマクロを定義しても大丈夫
#define VERSION 2
// これ以降は 2 の VERSION マクロが有効

マクロと関数の使い分け

 マクロ置き換えはとても便利な機能ですが、なんでもマクロを使えばいいというわけではありません。先ほどのMAXマクロを次のように使った場合はどうなるでしょうか。

#define MAX(a, b) ((a) > (b) ? (a) : (b))
int a = 1;
printf("%d\n", MAX(a++, 2)); // ?

 マクロが置き換えられると次のようになります。

int a = 1;
printf("%d\n", ((a++) > (2) ? (a++) : (2))); // ?

 1つの式の中に「a++」が2回現れました。Cでは1つの式の中(厳密には副作用完了点の間)でオブジェクトの値が2回以上変わるような式は書けないことになっています。従ってこのような書き方はできません。

 ところが多くのコンパイラはこの問題を検出できず、この正しくないコードがコンパイルできてしまいます。しかし、そのプログラムを実行した結果がどうなるかは決められていませんので、コンパイルできたからといって正しいプログラムであるとは限らないのです。このようなコードにならないよう、プログラマが注意してコードを書く必要があります。

 間違いを引き起こしやすい記述はなるべく避けるのが良い作法ですが、どうしてもこのように書きたい場合にはMAXマクロと同じ動作をする関数を用意するのが良いでしょう。

// マクロMAXの関数版
int max(int a, int b) { return a > b ? a : b; }

 ただしこれですと、int型以外のオブジェクトを渡して呼び出すことができませんので、型ごとに関数を用意する必要があります。マクロを使うのか、関数を使うのか、状況に応じて判断してください。

マクロでカッコを使う理由

 ところで、先のMAXマクロではカッコをたくさん使っています。これには理由があります。もしカッコを書かないと思わぬ結果になることがあるからです。

// もしカッコを書かなかったら...
#define MAX(a, b) a > b ? a : b
// これはOK。x = 2
int x = MAX(1, 2); // x = 2
// & は論理積。つまり2進数の 01 と 11 の論理積なので、y = 1
int y = 1 & 3; // y = 1
// 結果は z = 2 となるべきですが、この場合そうなりません
int z = MAX(1 & 3, 2);
printf("z = %d\n", z); // z = 1

 カッコがないMAXマクロで「MAX(1 & 3, 2)」を置き換えると次のようになります。

// #define MAX(a, b) a > b ? a : b
// MAX(1 & 3, 2)
1 & 3  >  2  ?  1 & 3 :  2

 少々分かりづらいですが、この中で最も優先順位の高い演算子は比較演算子「>」です。つまり「1 & 3」よりも先に「3 > 2」が処理され、この式の値は1になります。もちろんこれは意図した通りの動作ではありません。そこで、マクロの引数として渡された式が確実に優先的に処理されるようにするため、aやbにカッコを付けているわけです。

 次の例ではどうでしょうか。

#define MAX(a, b) (a) > (b) ? (a) : (b)
int x = MAX(2, 1) + 1;

 この例では、MAXマクロの全体を囲むカッコがありません。この式も「x = 2 + 1」とはなりません。置き換えてみると分かるように、後に付いている「+ 1」が三項演算子「?:」の最後の項の一部となってしまうためです。

int x = (2) > (1) ? (2) : (1) + 1;
// 上はつまり下と同じ
int x = (2) > (1) ? (2) : (1 + 1);

 マクロ置き換えは強力で便利ではありますが、むやみに使うと逆に混乱を招く場合もあります。適度なバランスがどの程度か見極めながら使っていくようにしましょう。

コンパイラのオプションでマクロを定義する

 オブジェクト形式マクロは、コンパイラのオプションでも指定できます。この方法もよく使われますので覚えておくとよいでしょう。

 GCCでは-Dオプションで指定します。例えば、「#define VERSION 3.14」と同等の定義をする場合は、次のように指定します。

> gcc -D VERSION=3.14 test.c

 Visual C++ でも「/D」オプションで指定できます。

> cl /DVERSION=3.14 test.c

 また、この指定は統合開発環境のGUIでも可能です。Pleiadesを使う場合は、プロジェクトのプロパティから、「C/C++ビルド」-「設定」-「ツール設定」-「GNU C Compiler」と順に選択し、「シンボル」で指定します。Visual C++の場合はプロジェクトのプロパティから、「構成プロパティ」-「C/C++」-「プリプロセッサ」と順に選択し、「プリプロセッサの定義」で指定します。

Pleiadesでマクロを定義する方法(左)とVisual C++ 2008でマクロを定義する方法(右)

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る