
C言語基礎|関数と関数形式マクロの違い
「引数ゼロでも“関数っぽい”。小さな定番処理を、読みやすく安全にまとめよう!」
関数形式マクロというと sqr(x) みたいに引数がある形を想像しがちですが、実は 引数が0個の関数形式マクロ も作れます。
ポイントはこれです。
- 見た目は関数呼び出し:alert() のように書ける
- 実体はマクロ展開:コンパイル前に式が埋め込まれる
- 短い定番処理を“名前付き”で使える:読みやすさアップ
今回は alert() の例を、よりシンプルで分かりやすい別例に置き換えながら、
「どう定義する?」「どう展開される?」「何に注意する?」を、表と図でしっかり説明します。
後半では、演習8-1〜8-3に似た問題も3つ、解答と解説つきで用意しますね。

引数のない関数形式マクロの基本
まず結論:引数がなくても () を付けて呼べる
引数なし関数形式マクロは、こんな形です。
引数なし関数形式マクロの形
| 種類 | 定義例 | 呼び出し例 | ざっくり意味 |
|---|---|---|---|
| 引数なし関数形式マクロ | #define NAME() 式 | NAME() | NAME() を式に展開 |
表の説明
NAME() という「() 付きの形」を見つけたら、後ろの式に展開する、というルールです。
NAME(カッコなし)とは別物として扱われやすいので、定義と呼び出しの両方で () を付けるのがコツです。
サンプルプログラム
ログの区切り線を出すマクロです。
- マクロ名:bar()
- 役割:区切り線を表示して改行する(ちょい便利)
サンプル:bar() を呼び出すだけの小さな例
プロジェクト名:chap8-3-1 ソースファイル名:chap8-3-1.c
#include <stdio.h>
#define bar() (puts("---- ここから処理を開始します ----"))
int main(void)
{
bar();
puts("計算が終わりました。");
return 0;
}実行例
---- ここから処理を開始します ----
計算が終わりました。図で理解:関数ではなく、展開されて“埋め込まれる”
引数ありの関数形式マクロと同じで、引数なしでも動きは「呼び出し」ではなく「展開」です。
図:プリプロセッサによる展開の流れ

図の説明
bar() は実行時にどこかへ飛ぶわけではなく、コンパイル前に puts("...") に置き換えられます。
つまり、ソースコードが“書き換えられてから”コンパイルされるイメージです。
展開例:bar() は最終的にどうなる?
呼び出し側のこの行は…
bar();
プリプロセッサによって、こう展開されます。
(puts("---- ここから処理を開始します ----"));
関数呼び出しとマクロ展開の違い(引数なし版でも同じ)
| 観点 | 関数 | 引数なし関数形式マクロ |
|---|---|---|
| 実行時の動き | 呼び出して戻る | 呼び出しではなく展開 |
| コードの増え方 | 本体は1か所 | 使う場所ごとに展開が増える |
| デバッグ | 呼び出しとして追える | 展開後の式を意識する必要あり |
表の説明
引数がなくても、マクロは「その場に貼り付く」ので、使いすぎるとコード量(結果のバイナリ)が増える可能性があります。
命令(ここで登場するもの)の書式と役割
ここでは、プログラム内で使った命令・要素を「何をするの?」目線で整理します。
#define の書式(引数なし関数形式マクロ)
書式
#define 名前() 置き換える内容
何をする?
名前() を見つけたら、右側の内容に展開します(コンパイル前)。
puts の書式
書式
puts("文字列");
何をする?
文字列を表示して、最後に改行も出してくれます。ログ表示に便利です。
printf の書式
書式
printf("書式文字列", 値, ...);
何をする?
数値を埋め込むなど、書式付きの出力を行います。
puts は簡単、printf は柔軟、という住み分けですね。
引数なしでも大事:マクロは ( ) で包むと安全
今回の定義はこうでした。
#define bar() (puts("---- ここから処理を開始します ----"))
この ( ) は、将来こういう使い方をしても事故りにくくするためです。
- if の後に置く
- 別の式の一部にする(おすすめはしないけど、事故耐性は上がる)
なぜ ( ) で包む?
| 目的 | 効果 |
|---|---|
| 優先順位の事故を防ぐ | 展開先が他の演算子と混ざっても崩れにくい |
| 見た目を“式”として安定させる | 展開後の形が読みやすくなる |
注意:引数なしでも “副作用” はあり得る
「引数がないなら安全でしょ?」と思いがちですが、展開先が何をするか次第です。
たとえば、もしこんなマクロを作ると…
#define tick() (counter++)
tick() を呼ぶたびに値が変わります。これは意図通りならOKですが、
条件式の中などで使うと、分かりにくい動作になりやすいです。
引数なしマクロの使いどころ
| 目的 | 向いている例 | 理由 |
|---|---|---|
| 表示・ログ | bar(), debug_on() | 効果が分かりやすい |
| 定数っぽい値 | pi() を 3.14159 に展開など | 読みやすさが上がる |
| 状態を変える処理 | tick() で counter++ | 使いどころを限定しないと混乱しやすい |
マクロ定義内の式は ( ) で囲む(超重要)
ここは引数ありの話ですが、引数なしでも“考え方”は同じです。
関数形式マクロは 展開された結果が、別の演算子と組み合わさるので、( ) がないと壊れます。
悪い例:全体を囲まない sum_of
#define sum_of(x, y) x + y
これをこう使うと…
z = sum_of(a, b) * sum_of(c, d);
展開はこうなり、意図が崩れます。
z = a + b * c + d;
良い例:引数と全体を ( ) で囲む
#define sum_of(x, y) ((x) + (y))
展開後はこうなり、安全です。
z = ((a) + (b)) * ((c) + (d));
図:なぜ壊れるのか(優先順位のイメージ)

図の説明
C言語は掛け算 * が足し算 + より優先されます。
だから ( ) で守らないと、勝手に計算順が変わってしまいます。
演習問題
ここからは、同じノリで手を動かせる練習問題です!
演習8-1
二つの値の小さいほうを返す関数形式マクロを定義せよ
仕様:min(x, y) は小さいほうの値を返す。
解答例
#define min(x, y) (((x) < (y)) ? (x) : (y))
解説
- 引数 (x), (y) を ( ) で囲う。
- 全体も ( ) で囲う。
- 比較結果で返す値が変わるので条件演算子を使う。
演習8-2
三つの値 a, b, c の中央値(真ん中の値)を返すマクロを定義せよ
仕様:mid(a, b, c) は3つのうち“真ん中”を返す(ソートは不要、式でOK)
解答例
#define mid(a, b, c) (max(min((a),(b)), min(max((a),(b)), (c))) ) )
…は長いので、まずは補助マクロを作って安全に書くのがおすすめです。
#define max(x, y) (((x) > (y)) ? (x) : (y))
#define min(x, y) (((x) < (y)) ? (x) : (y))
#define mid(a, b, c) (max(min((a),(b)), min(max((a),(b)), (c))))
解説
- こういう複雑な式ほど、( ) で守らないと崩れます。
- ただし注意:引数に a++ みたいな副作用を入れると危険です。
演習8-3
二つの変数を交換するマクロを定義せよ
仕様:swap(type, a, b) で a と b を入れ替える。
解答例
#define swap(type, a, b) do { type tmp = (a); (a) = (b); (b) = tmp; } while (0)
解説
- do { ... } while (0) の形にしておくと、if の中で使っても安全になりやすい。
- (a) や (b) を ( ) で囲って、式の崩れを防ぐ。
- 交換は「一時変数 tmp」を使うのが王道
