
C言語基礎|マクロで広がるC言語プログラミング
「同じ処理、型ごとに何回書く?─マクロで“1回”にまとめよう。」
C言語って、シンプルで速くて、書いたとおりに動いてくれるのが最高なんですが……その反面、型が違うだけで同じような関数を何個も作りたくなること、ありますよね。
たとえば「小さいほうを返す」「上限・下限に収める」「2つを比較する」みたいな処理は、intでもdoubleでもやりたいのに、関数として書くと min_int、min_double…と増えていきがちです。
そこで活躍するのが 関数形式マクロ(function-like macro)。
これは「関数っぽい形で呼び出せるのに、実体は置換・展開でできる」仕組みです。うまく使うと、同じ書き方で複数の型に対応できて、プログラムの見通しがグッと良くなります。
この記事では、関数形式マクロの基本と注意点を、表と図でしっかり解説します。

まずは「型ごとに関数を作り分ける」例
ここでは「2つの値のうち小さいほうを返す」処理を題材にします。
サンプル1:int用とdouble用で関数を分ける
プロジェクト名:chap8-1-1 ソースファイル名:chap8-1-1.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
// 整数と実数の小さいほうを返す(関数で作り分け)
#include <stdio.h>
//--- int型:小さいほうを返す ---//
int min_int(int a, int b)
{
return (a < b) ? a : b;
}
//--- double型:小さいほうを返す ---//
double min_double(double a, double b)
{
return (a < b) ? a : b;
}
int main(void)
{
int ai, bi;
double ad, bd;
printf("整数を2つ入力してください:");
scanf("%d%d", &ai, &bi);
printf("小さいほうは%dです。\n", min_int(ai, bi));
printf("実数を2つ入力してください:");
scanf("%lf%lf", &ad, &bd);
printf("小さいほうは%fです。\n", min_double(ad, bd));
return 0;
}実行例
整数を2つ入力してください:8 3
小さいほうは3です。
実数を2つ入力してください:2.5 9.1
小さいほうは2.500000です。何が困りやすい?
同じロジックなのに、型が違うだけで関数名が増えます。
| やりたいこと | int | double | 型が増えると… |
|---|---|---|---|
| 小さいほうを返す | min_int | min_double | long, float, unsigned…でさらに増える |
この「型の数だけ似た関数が増える問題」を、関数形式マクロでスッキリさせます。
関数形式マクロで「同じ書き方」にまとめる
サンプル2:関数形式マクロ min(a, b)
プロジェクト名:chap8-1-2 ソースファイル名:chap8-1-2.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
// 整数と実数の小さいほうを返す(関数形式マクロ)
#include <stdio.h>
#define min(a, b) ((a) < (b) ? (a) : (b))
// マクロ名と(の間に空白を入れない(min (a,b) は別扱いになりやすい)
int main(void)
{
int ai, bi;
double ad, bd;
printf("整数を2つ入力してください:");
scanf("%d%d", &ai, &bi);
printf("小さいほうは%dです。\n", min(ai, bi));
printf("実数を2つ入力してください:");
scanf("%lf%lf", &ad, &bd);
printf("小さいほうは%fです。\n", min(ad, bd));
return 0;
}実行例
整数を2つ入力してください:8 3
小さいほうは3です。
実数を2つ入力してください:2.5 9.1
小さいほうは2.500000です。同じ min(ai, bi) / min(ad, bd) という書き方で、intでもdoubleでも動きます。ここが気持ちいいポイントです。
図でつかむ:マクロは「置換」ではなく「展開」
第5章のオブジェクト形式マクロが「文字列の置換」っぽかったのに対し、関数形式マクロは「形を保ったまま展開」されます。
図:翻訳の流れ(マクロが効く場所)

この図の意味
min(a, b) はコンパイル前に中身へ展開されるので、コンパイラは「すでに展開された式」として扱います。
展開例:実際にどう広がる?
次の1行があったとします。
printf("小さいほうは%dです。\n", min(ai, bi));
プリプロセッサにより、こう展開されます。
printf("小さいほうは%dです。\n", ((ai) < (bi) ? (ai) : (bi)));
関数とマクロの見え方の違い
| 項目 | 関数 | 関数形式マクロ |
|---|---|---|
| 実体 | 実行時に呼び出す | コンパイル前に展開される |
| 型への対応 | 型ごとに関数名が増えやすい | 同じ書き方で通りやすい |
| デバッグ | 呼び出しとして追いやすい | 展開後の式を意識する必要あり |
| 注意点 | 引数は1回評価 | 引数が複数回評価されることがある |
ここで登場する「命令」「書式」をやさしく整理
この章のプログラムには、Cの基本部品がいくつか出てきます。ひとつずつ役割を確認します。
#include の書式と役割
書式
#include <ヘッダファイル名>
何をする?
標準ライブラリなどの宣言を読み込みます。今回の <stdio.h> は、printf や scanf を使うために必要です。
| 例 | 意味 |
|---|---|
| #include <stdio.h> | 入出力(printf/scanf)の宣言を使えるようにする |
#define の書式と役割(関数形式マクロ)
書式(関数形式マクロ)
#define マクロ名(仮引数, ...) 展開後の式
何をする?
マクロ名(…) の形を見つけたら、展開後の式に広げるという指示です。
| 例 | 意味 |
|---|---|
| #define min(a, b) ((a) < (b) ? (a) : (b)) | min(x,y) を条件演算子の式に展開する |
大事ポイント
- マクロ名と(の間に空白を入れない(見た目は似てても扱いが変わることがあります)
- 引数も全体も ( ) でしっかり囲う(後で説明します)
printf / scanf の書式と役割
printf の書式
printf("書式文字列", 値, ...);
画面に出力します。
scanf の書式
scanf("書式文字列", 変数のアドレス, ...);
キーボード入力を読み取り、変数に入れます。scanf は「入れ物の場所」が必要なので、&ai のようにアドレスを渡します。
| 書式指定 | 意味 | 対象例 |
|---|---|---|
| %d | int の入力/出力 | int ai |
| %lf | double の入力 | double ad |
| %f | double の出力 | double ad |
なぜマクロ定義は ( ) だらけなの?
マクロは展開されるので、展開後の式が他の演算子と混ざっても安全にする必要があります。
よくある事故1:全体を囲わない
もし、こう書いてしまうと…
#define min(a, b) (a < b ? a : b)
一見動きそうですが、別の式に混ざると読みにくく、優先順位が絡む場面で危険になります。
なので 全体も ( ) で包むクセをつけるのが安心です(今回の例は条件演算子なので特に丁寧に包みます)。
よくある事故2:引数を囲わない
もし、こう書いてしまうと…
#define min(a, b) (a < b ? a : b)
min(x + 1, y) のような呼び出しで、展開後が読みにくくなり、意図せず優先順位の影響を受けることがあります。
だから (a) や (b) のように引数を必ず囲うのが定石です。
超重要:マクロは「引数が複数回評価される」ことがある
ここが関数と一番違うところです。
たとえば、次の呼び出しは危険です。
min(i++, j++)
min(a, b) は a と b を条件判定と結果でそれぞれ使うので、展開すると a や b が 複数回登場しやすいです。
結果として i++ が2回実行されてしまう、みたいな事故が起こりえます。
安全な渡し方・危険な渡し方
| 渡すもの | 安全? | 理由 |
|---|---|---|
| 変数(ai, bi) | 安全 | 何回使われても値が変わらない |
| 定数(10, 3.14) | 安全 | 何回使われても同じ |
| i++ や 関数呼び出し | 危険 | 展開で複数回実行される可能性 |
コツ:マクロには「副作用のない式」を渡す、が鉄則です。
まとめ:関数形式マクロは“便利さ”と“注意点”がセット
| うれしいこと | 気をつけること |
|---|---|
| 同じ書き方で複数の型に対応しやすい | 引数が複数回評価される可能性 |
| 小さな処理をスッキリ書ける | ( ) を丁寧に付けて事故防止 |
| コードの重複を減らせる | デバッグ時は展開後の形を意識 |
