
C言語入門|マクロ関数のしくみと注意点
― 関数のように見える、ただの文字列置換
#define は、数値や文字列を置き換えるだけの命令だと思っていませんか?
実は #define には、引数を受け取る形も用意されています。
この仕組みを使うと、見た目はまるで関数のような記述ができるのです。
ただし、ここには C言語らしい落とし穴 が待っています。

引数を持つ #define ― マクロ関数
まずは、次の例を見てみましょう。
サンプルプログラム
プロジェクト名:13-6-1 ソースファイル名: sample13-6.1.c
#include <stdio.h>
#define ADD(X, Y) X + Y
int main(void)
{
printf("%d\n", ADD(4, 9));
return 0;
}7行目を見ると、ADD という関数を呼び出しているように見えますね。
しかし、これは 関数ではありません。
ADD は、あくまで #define による置換ルールです。
プリプロセッサは、ADD(4, 9) を次のように置き換えます。
printf("%d\n", 4 + 9);このように、引数を伴った文字列置換を行う #define を
マクロ関数 と呼びます。

マクロ関数は「関数」ではない
マクロ関数は、第8章で学んだ通常の関数とはまったく別物です。
| 項目 | 通常の関数 | マクロ関数 |
|---|---|---|
| 処理タイミング | 実行時 | コンパイル前 |
| 型チェック | 行われる | 行われない |
| 実体 | 機械語として存在 | 文字列置換のみ |
マクロ関数は、
少し賢くなった文字列置換機能
にすぎません。
そのため、マクロ定数と同様の注意点に加えて、
さらに厄介な副作用が発生する可能性があります。
予期しない動作が起きる例
次のコードを見て、結果を想像してみてください。
printf("%d\n", ADD(4, 9) * 2);もし ADD が通常の関数なら、
- ADD(4, 9) → 13
- 13 * 2 → 26
となり、26が表示されるはずです。
しかし、マクロ関数では次のように置換されます。
printf("%d\n", 4 + 9 * 2);C言語では、掛け算が足し算より優先されるため、
- 9 * 2 → 18
- 4 + 18 → 22
結果は 22 になります。
これは、マクロ関数が
式全体を考慮せず、単純に文字列を並べただけ
だから起こる現象です。
カッコで守る ― マクロ関数の基本ルール
このような事故を防ぐために、
マクロ関数には「お作法」があります。
#define ADD(X, Y) ((X) + (Y))こうしておけば、
ADD(4, 9) * 2は次のように展開されます。
((4) + (9)) * 2これなら、先に足し算が行われ、
期待どおり 26 が得られます。
マクロ関数の安全ルール
- 引数は必ずカッコで囲む
- 置換後の式全体もカッコで囲む
この2点は、必須事項として覚えておきましょう。
それでもマクロが使われてきた理由
ここまで読むと、こう思うかもしれません。
「危ないなら、普通の関数を使えばいいのでは?」
その判断は、現代の開発では正解です。
ただし、C言語が誕生した当初は、
- CPU性能が低い。
- メモリが極端に少ない。
という環境でした。
マクロ関数は、
- 関数呼び出しのオーバーヘッドがない。
- 展開後は単なる式になる。
という理由から、
速度とメモリ効率を最優先する場面で重宝されてきたのです。
現代C言語におけるマクロ関数の立ち位置
現在では、
- 高性能なCPU
- 十分なメモリ
- 高度な最適化を行うコンパイラ
が当たり前になっています。
インライン展開や最適化によって、
通常の関数でもマクロと同等、あるいはそれ以上の効率が得られます。
そのため、
- 新規開発で積極的に使う理由は少ない。
- ただし、既存コードを読むためには必須知識
という位置づけになっています。
マクロ関数との付き合い方
マクロ関数は、
- 強力
- 危険
- 読みにくい
という性質を併せ持っています。
マクロ利用ポリシー
積極的に使う理由はあまりない。
しかし、理解しておく理由は非常に大きい。
過去の資産やライブラリを正しく理解するためにも、
マクロ関数の仕組みと注意点は、しっかり頭に入れておきましょう。
