
C言語基礎|コンマ演算子を用いた関数形式マクロ
「if の中でマクロが壊れる理由、ちゃんと説明できますか?─コンマ演算子で安全にまとめるコツ」
コンマ演算子を用いた関数形式マクロって、何がうれしいの?
関数形式マクロは、見た目は関数呼び出しっぽいのに、実体はプリプロセッサによる展開(置き換え)です。
この性質のおかげで「便利に書ける」反面、複数の処理をまとめようとして { } を使うと、if–else の形が崩れて翻訳時エラーになったりします。
そこで活躍するのが コンマ演算子。
複数の式を 1つの式 にまとめられるので、マクロの展開結果を「単一の式文」にできて、if–else の中でも安全に使えるようになります。
この記事では、わざと失敗するマクロ → なぜ失敗するか → コンマ演算子で直す、という流れで、表と図つきでしっかり解説しますね。

まずは失敗例:複合文 { } を返すマクロは if–else と相性が悪い
次のサンプルは、記号を出してからメッセージを表示するマクロです。内容はシンプルですが、わざと “危ない形” にしています。
サンプル(誤り):複合文に展開されるマクロ
プロジェクト名:chap8-4-1 ソースファイル名:chap8-4-1.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
#include <stdio.h>
// 記号を出してからメッセージ表示(誤り)
#define mark_puts(msg) { putchar('>'); puts(msg); }
int main(void)
{
int n;
printf("整数を入力してください:");
scanf("%d", &n);
if (n > 0)
mark_puts("プラスの値です。");
else
mark_puts("ゼロ以下の値です。");
return 0;
}このプログラムは、翻訳時エラーになりやすいです(コンパイラは else が迷子になります)。
展開後に if が途中で終わってしまう
上の if 文を「マクロ展開後の姿」にすると、こうなります。
誤ったマクロの展開(イメージ)
if (n > 0)
{ putchar('>'); puts("プラスの値です。"); } ;
else
{ putchar('>'); puts("ゼロ以下の値です。"); } ;
何が起きている?
ポイントはここです。
- { putchar(...); puts(...); } は 複合文(1つの文)
- その直後の ; は 空文(何もしない1文)
- つまり if (n > 0) が担当するのは、最初の { ... } までで完結してしまう
そのあとに ; が独立した文として現れ、さらに else が出てくる
コンパイラ目線だと、
「if がもう終わっているのに、どうして else があるの?」
となってエラーになります。
じゃあ { } を消せばいい?
例えばこうすると…
#define mark_puts(msg) putchar('>'); puts(msg);
今度は if が putchar だけを担当して、puts は常に実行される みたいな “別の事故” を呼びやすいです。
つまり、複数処理を素直に書くほど、if–else の文のまとまりと衝突しやすいんですね。
解決策:コンマ演算子で「複数の式」を「単一の式」にする
ここで使うのが コンマ演算子 です。
Table 1 コンマ演算子のルール
| 形 | 意味 |
|---|---|
| a, b | a を評価して捨て、次に b を評価する。式全体の型と値は b の結果になる。 |
表の説明
コンマでつないだ a, b は 1つの式 です。
中では左から順に評価されますが、最終的な「式の値」は右側(b)になります。
正しい書き方:マクロを 1つの式に展開させる
さっきのマクロを、コンマ演算子で書き直します。
サンプル(正しい):コンマ演算子で単一の式にする
プロジェクト名:chap8-4-2 ソースファイル名:chap8-4-2.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
#include <stdio.h>
// 記号を出してからメッセージ表示(正しい)
#define mark_puts(msg) (putchar('>'), puts(msg))
int main(void)
{
int n;
printf("整数を入力してください:");
scanf("%d", &n);
if (n > 0)
mark_puts("プラスの値です。");
else
mark_puts("ゼロ以下の値です。");
return 0;
}実行例
整数を入力してください:0
>ゼロ以下の値です。図で理解:if の中身が「式文 1個」になる
正しいマクロの展開(イメージ)
if (n > 0)
(putchar('>'), puts("プラスの値です。"));
else
(putchar('>'), puts("ゼロ以下の値です。"));
ポイント
- (putchar(...), puts(...)) は 式
- その式に ; が付くと 式文(単一の文) になる
- だから if と else の対応関係が崩れません
重要:複数の式に置換するマクロは「単一の式」になるように作る
ここ、この記事の核心です。
複数処理マクロの安全度
| 展開先 | if–else での安全性 | なぜ |
|---|---|---|
| { 文; 文; } | 危険 | if の文が終わった扱いになりやすい。 |
| 文; 文; | 危険 | if が1文しか担当しない。 |
| (式, 式) | 安全 | 1つの式 → 1つの式文として扱える。 |
表の説明
if の後ろに置けるのは「1つの文」です。
コンマ演算子を使って「1つの式」にしておけば、最後に ; を付けて「1つの式文」になり、構文が安定します。
コンマ演算子の性質を、もう少しだけ丁寧に
コンマ式 a, b は次の性質があります。
- a が先に評価される(副作用があればここで起きる)
- a の値は捨てられる
- b が評価され、式全体の型と値になる
例:右側の結果が代入される
int i = 3;
int j = 5;
int x;
x = (++i, ++j);
このときの流れはこうです。
評価順序のイメージ
++i を実行 → i は 4(この値は捨てる)
++j を実行 → j は 6(この値が式全体の値)
x に 6 が代入される
この章で登場する命令・記号の書式と役割
ここからは、出てきた要素を「何をするもの?」で整理します。
#define の書式(関数形式マクロ)
書式
#define マクロ名(仮引数) 置換内容
何をする?
ソース中の マクロ名(…) を、コンパイル前に 置換内容 へ展開します。
今回の mark_puts(msg) は、msg の部分に引数が入り、(putchar(...), puts(msg)) に展開されます。
if / else の書式
書式
if (条件){
文;
}else{
文;
}
何をする?
条件が真なら if 側の文を実行し、偽なら else 側の文を実行します。
大事なのは、if (条件) の直後に置けるのは 文が1つ という点です(ここにマクロが食い込むと事故が起きます)。
putchar の書式
書式
putchar(文字);
何をする?
1文字を出力します。今回の例では > を表示して、ログっぽい見た目を作っています。
puts の書式
書式
puts("文字列");
何をする?
文字列を表示し、最後に改行も出します。短いメッセージ表示に便利です。
printf / scanf の書式(入力を受け取る部分)
printf
printf("書式文字列", 値, ...);
表示用(問いかけ文を出す)。
scanf
scanf("書式文字列", 変数のアドレス, ...);
入力用(今回なら %d で int を読む)。
&n は n の格納場所(アドレス)を渡しています。
セミコロン ; の意味
- 式; は 式文(その式を実行する1つの文)
- ; だけだと 空文(何もしない1つの文)
失敗例では、この ; が構文を崩す引き金になりました。
まとめ:コンマ演算子マクロの使いどころ
- 複数の処理をマクロでまとめたいとき
- しかも if–else の中でも安全に使いたいとき
- その場合は (式, 式) で 1つの式にして、式文として扱える形にする。
これで、見た目が関数っぽいマクロでも、構文事故をかなり減らせます。
