
C言語基礎|関数と関数形式マクロの違い
「見た目は同じでも、中身は別モノ。関数とマクロの“違い”がバグを減らす!」
C言語を書いていると、関数みたいに呼べるのに #define で作る“関数形式マクロ”に出会います。
ぱっと見は foo(x) で同じ形なのに、動き方はけっこう別物なんですよね。
- 関数は「呼び出す」
- 関数形式マクロは「展開される(貼り付けられる)」
この違いを知らないまま使うと、思わぬ副作用(意図しない増減、関数が2回呼ばれる等)が起きがちです。
ここでは、シンプルな別例を使って「違い」と「安全に使うコツ」を、表と図でしっかり整理します。

まずは題材:絶対値を求める(関数版 / マクロ版)
ここでは分かりやすい 絶対値 abs を求めるプログラムを例に解説していきます。
サンプルA:関数で絶対値を求める
プロジェクト名:chap8-2-1 ソースファイル名:chap8-2-1.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
// 絶対値(関数)
#include <stdio.h>
int abs_int(int x)
{
return (x < 0) ? -x : x;
}
int main(void)
{
int n;
printf("数値を入力してください:");
scanf("%d", &n);
printf("絶対値は%dです。\n", abs_int(n));
return 0;
}実行例
数値を入力してください:-7
絶対値は7です。サンプルB:関数形式マクロで絶対値を求める
プロジェクト名:chap8-2-2 ソースファイル名:chap8-2-2.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
// 絶対値(関数形式マクロ)
#include <stdio.h>
#define ABS(x) ((x) < 0 ? -(x) : (x))
int main(void)
{
int n;
printf("数値を入力してください:");
scanf("%d", &n);
printf("絶対値は%dです。\n", ABS(n));
return 0;
}実行例
数値を入力してください:-7
絶対値は7です。見た目の結果は同じ。
でも「中で起きていること」は結構違います。
関数は呼び出し、マクロは展開
図:処理のイメージの違い

この図の説明
- 関数は、プログラムの流れが abs_int に移動して戻ってきます(呼出しと復帰)。
- マクロは、コンパイル前に ABS(n) が式へ展開され、main の中に貼り付けられます。
ポイント1:型の扱いが違う(関数は型を決める、マクロは式に広く適用)
型に関する違い
| 観点 | 関数 | 関数形式マクロ |
|---|---|---|
| 引数の型 | 定義時に決まる(例:int) | 基本的に型を固定しない |
| 返却値の型 | 定義時に決まる | 展開された式の型に依存 |
| 型が増えたとき | abs_int, abs_long…と増えがち | ABS(x) は比較と単項-ができれば使える |
表の説明
関数は「この型で受け取る」と決めて作ります。だから型ごとに関数名が増えやすいです。
マクロは「式の形を広げる」ので、比較 < や - ができる型なら、同じ書き方で適用しやすくなります。
ポイント2:関数は“内部で作業が起きる”が、マクロは起きない
関数呼び出しでは、私たちが意識しないところで次が行われます。
関数呼び出しで起きること
| 起きること | ざっくり説明 |
|---|---|
| 引数の受け渡し | 実引数の値が仮引数へコピーされる |
| 呼び出しと復帰 | main から関数へ移動し、戻ってくる |
| 返却値の受け渡し | 戻り値を main 側で受け取る |
一方マクロは、式として貼り付くので、これらの「呼び出し作業」はありません。
速度とサイズの傾向(ざっくり)
| 観点 | 関数 | 関数形式マクロ |
|---|---|---|
| 実行速度 | 呼び出し分のコストがある場合も | 呼び出しがない分、わずかに有利なことも |
| 実行ファイルのサイズ | 関数本体は1つ | 展開先が多いと大きくなりやすい |
表の説明
マクロは使った場所ごとに式が埋め込まれます。複雑な式をあちこちで使うと、その分だけサイズが増えやすい、というわけです。
ポイント3:副作用(side effect)が起きやすいのはマクロ
ここが超重要です。
マクロは展開されるので、引数が式の中で複数回出てくると、その式が複数回評価されることがあります。
ABS(a++) の危険な展開
呼び出し側がこう書いたら…
ABS(a++)
展開後はこうなります。
((a++) < 0 ? -(a++) : (a++))
この図の説明
a++ が条件判定でも結果でも出てきます。つまり状況によっては a++ が複数回実行され、a が想定以上に増えます。
これが「副作用が見えにくい」典型例です。
安全な引数 / 危険な引数
| 引数の例 | 安全? | 理由 |
|---|---|---|
| n, x, value(ただの変数) | だいたい安全 | 何回参照しても値が変わらない |
| 10, -3.5(定数) | 安全 | 何回でも同じ |
| a++, i += 2 | 危険 | 評価回数が増えると値が変わる |
| func() | 危険 | 関数が2回呼ばれる可能性がある |
関数なら副作用が起きにくい理由
関数 abs_int をこう呼んでも…
abs_int(a++)
a++ は「引数を作る時点」で一度だけ評価され、abs_int の中では仮引数 x として扱われます。
だから a++ が2回走る、みたいなことは基本起きません(関数は引数がコピーされるため)。
超重要:関数形式マクロとオブジェクト形式マクロの違い(空白の罠)
関数形式マクロは、マクロ名の直後に ( が続く形で定義します。
関数形式マクロの書式
#define 名前(引数) 展開する式
ところが、次のように 名前と(の間に空白が入ると…
#define ABS (x) ((x) < 0 ? -(x) : (x))
これは関数形式マクロではなく、オブジェクト形式マクロっぽく扱われて、
「ABS を (x) ((x) < 0 ? -(x) : (x)) に置換する」
みたいな、意図しない状態になりがちです。
空白あり/なしの意味
| 定義 | 扱われ方 | 意図 |
|---|---|---|
| #define ABS(x) ... | 関数形式マクロ | ABS(値) を式へ展開したい |
| #define ABS (x) ... | 別物になりやすい | ABS 自体を文字列的に置換しがち |
結論:関数形式マクロは、マクロ名と(の間に空白を入れないのが鉄則です。
命令(ここで使ったもの)の書式と役割まとめ
#include の書式
#include <stdio.h>
何をする?
printf と scanf を使うための宣言を読み込みます。
#define の書式(関数形式マクロ)
#define ABS(x) ((x) < 0 ? -(x) : (x))
何をする?
ABS(何か) を見つけたら、後ろの式へ展開します(コンパイル前に展開)。
printf の書式
printf("書式文字列", 値, ...);
何をする?
画面にメッセージを表示します。
scanf の書式
scanf("書式文字列", 変数のアドレス, ...);
何をする?
入力を読み取り、変数に格納します。
%d は int 用、%lf は double 用です。
まとめ:使い分けのコツ
| こうしたい | 向いているのは |
|---|---|
| 副作用の危険を減らしたい | 関数 |
| 型ごとに同じ処理を手軽に書きたい | 関数形式マクロ(ただし注意して使う) |
| 大きい処理を何度も使う | 関数(サイズ増加を避けやすい) |
| 小さく単純な式を高速に展開したい | 関数形式マクロ |
