Menu

C言語のマクロ: #define、関数形式マクロ、そして括弧の規則

#define によるオブジェクト形式・関数形式のマクロ - なぜすべての引数と本体全体に括弧が要るのか、多重評価の罠、do-while(0) による複数行マクロ、そして関数や const のほうが適している場面。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

マクロとは、コンパイラが走る前にプリプロセッサが何らかのテキストへ置き換える名前のことです。仕組みはそれがすべてであり、このページの規則はすべてそこから導かれる帰結です。マクロは型を持たず、スコープを尊重せず、引数を評価せず、関数呼び出しが何なのかも知りません。テキストをコピーするだけです。

注意深く使えば欠かせないものです - 移植性のスイッチ、行番号を捉えるアサーションマクロ、配列サイズに使えるコンパイル時定数。不注意に使えば、ファイルを展開してみるまでありえないように見えるバグを生みます。

オブジェクト形式マクロ

最も単純な形は、値を表す名前を定義するものです:

慣例としてこれらの名前は UPPER_SNAKE_CASE にします。そうすれば読み手は、その記号が変数ではなくマクロであることを一目で判断できます。ここではこの慣例が普段以上に重要です。マクロはスコープを無視するからです。関数の中の #define でもファイルの残り全体に適用され、別の関数にある同じ名前の変数も平気で書き換えてしまいます。

#undef NAME は定義を削除するので、再定義の警告なしに後からその名前を定義し直せます。

関数形式マクロ

名前の直後に - 開き括弧の前に空白を入れずに - 引数リストを置くと、マクロは引数を取るようになります:

空白の規則は本物です。#define SQUARE (x) ((x)*(x)) は、置換テキストが (x) から始まる SQUARE という名前のオブジェクト形式マクロを定義します。その後に出るコンパイルエラーは、空白のことなど一言も触れてくれません。

括弧の規則

規則は 2 つあり、どちらも、マクロを書いているときには見えない式へ本体が貼り付けられるという事実から生まれます。

規則 1: すべての引数を括弧で囲む。 これがないと、それ自体が式である引数が演算子の優先順位によって引き裂かれます:

#define SQUARE_BAD(x) x * x

SQUARE_BAD(2 + 3)   /* 2 + 3 * 2 + 3 になる == 25 ではなく 11 */

規則 2: 本体全体を括弧で囲む。 これがないと、周囲の式が結果を引き裂きます:

#define DOUBLE_BAD(x) (x) + (x)

10 / DOUBLE_BAD(5)  /* 10 / (5) + (5) になる == 1 ではなく 7 */

両方の失敗を、修正版と並べて実行してみましょう:

悪い版はどちらも警告 1 つ出さずにコンパイルが通り、間違った数値を出します。身につけるべき習慣は機械的です。各引数を包み、それから結果を包む((a) > (b) ? (a) : (b)) はうるさく見えますが、そのうるささこそが狙いです。

多重評価の罠

括弧では 2 つ目の危険から身を守れません。マクロは引数のテキストを、その引数が現れるすべての場所へ貼り付けるので、副作用のある引数は 2 回以上起こります:

このマクロは ((i++) > (j) ? (i++) : (j)) に展開されます。比較が i を 6 へインクリメントしつつ 5 と 3 を比べ、真の側の分岐が i をさらに 7 へインクリメントして 6 を返します。つまり m は 6、i は 7 - どちらも「ij の大きいほう」が与えるべき結果ではありません。関数 max(i++, j) なら正しくなります。引数が呼び出しの前に一度だけ評価されるからです。

同じ罠は副作用のあるものすべてを捕らえます。SQUARE(rand())rand() を 2 回呼び、異なる 2 つの数を掛け合わせます。CHECK(read_byte()) は 2 バイト消費します。

防御策は 2 つ、好ましい順に:

  1. 本物の関数を使う。複数の型で必要なら型ごとに 1 つ書くか、ヘッダに static inline 関数を置きます。
  2. どうしてもマクロにするなら、引数が複数回評価されることを目立つように書き残し、呼び出し側を副作用のない状態に保ちましょう。

複数行マクロと do-while(0)

最後の行を除く各行の末尾をバックスラッシュで終えれば、マクロの本体は複数行にまたがれます。素朴な版は問題なさそうに見えます:

#define LOG_TWICE(msg) \
    printf("%s\n", msg); \
    printf("%s\n", msg)

そして、いちばん問題になる場所で、静かに壊れます:

if (verbose)
    LOG_TWICE("hello");
else
    printf("quiet\n");

置換後、if が持つのは最初の printf だけで、2 つ目は無条件に実行され、else は対応する if を失います - どこを指しているのか分からないメッセージを伴うコンパイルエラーです。本体をただの波かっこで包んでも同じことです。LOG_TWICE("hello") の後の ; が空文となって if を終わらせ、else がまた壊れます。

うまくいく定石が do { ... } while (0) です:

do { ... } while (0) は 1 つの文で、本体をちょうど 1 回実行し、しかも後ろのセミコロンを必要とします - そのため呼び出し側は普通の関数呼び出しのように読め、あらゆる制御フローの文脈で関数のように振る舞います。バックスラッシュには注意してください。バックスラッシュの後にはぐれた空白があると継続が途切れ、わけの分からないエラーになります。

定義済みマクロとアサーションのパターン

プリプロセッサ自身がいくつかのマクロを定義しており、それらこそが「どうしても関数にはできないもの」がある理由です:

__FILE____LINE__書かれた場所で展開されるので、マクロの中に置けば呼び出し側の位置を捉えられます。関数ではこれはできません - 関数の中では常にそのログ関数自身のファイルと行を報告することになります。標準の assert マクロが失敗した式の位置を報告するのは、まさにこの仕組みです。

その他の便利な定義済みの名前: __DATE____TIME__、そして __STDC_VERSION__(たとえば C17 なら 201710L)。

知っておく価値のある 2 つの演算子

マクロ本体の中では、# が引数を文字列リテラルに変え(「文字列化」)、## がトークン同士を貼り合わせます(「トークン連結」)。

SHOW(width * height)printf("width * height" " = %d\n", (width * height)); になります - 隣り合う文字列リテラルはコンパイラが連結するので、1 回の呼び出しで式のテキストとその値の両方が表示されます。覚えておく価値のあるデバッグの小技です。

## はもっとまれで、主にコード生成のマクロに現れます。#define MAKE_VAR(n) int var_##nMAKE_VAR(3)int var_3 にします。使うのは控えめに。プリプロセッサが組み立てた識別子は名前で検索できないため、コードをたどるのが難しくなります。

マクロ、関数、const の使い分け

仕事をこなせる中で最も弱い道具を使いましょう:

  • const 変数 - const double PI = 3.14159; は型を持ち、スコープに従い、デバッガに現れ、無関係なヘッダに再定義されることもありません。実行時に使う値にはこちらを優先しましょう。
  • enum - enum { MAX_USERS = 100 }; は、C がコンパイル時定数を要求する場所で使える名前付きの整数定数を、型とスコープ付きで与えます。整数の上限値に向いています。
  • static inline 関数 - 型チェックされ、各引数をちょうど 1 回評価し、現代のコンパイラならマクロと同じようにインライン展開してくれます。ほぼすべての関数形式マクロに対する正しい置き換えです。
  • マクロ - __LINE__ が必要なとき、型が存在するより前に動かなければならないとき(古いコードの配列サイズ)、条件付きコンパイルでコードを切り替えるとき、あるいは繰り返しの多いコードを生成するとき。

マクロが依然として他に譲らない場面の 1 つが配列のサイズ計算です。結果がコンパイル時定数でなければならないからです:

これにはこれでよく知られた注意点があります。正しいのは本物の配列に対してだけ、ということです。ポインタを渡すと - 配列は関数へ渡されるとポインタになります - sizeof はポインタを測ってしまい、間違っているのに自信ありげに黙ったままの答えを返します。

マクロをデバッグする

マクロがおかしな振る舞いをしたら、にらみつけるのはやめて展開しましょう:

gcc -E program.c | tail -30

置換後のテキストを見れば、問題が括弧の抜けなのか、二重評価なのか、他の何かとの名前の衝突なのかがすぐ分かります。-Wall -Wextra 付きでコンパイルするのも忘れずに - GCC と clang は「in expansion of macro」という注記を添え、報告されたエラー行を定義まで結び付けてくれます。

次はヘッダファイルです。そこでは #define#include が組み合わさり、同じ宣言を貼り付け直すことなく、1 つのプロジェクトが多数のソースファイルにまたがれるようになります。

よくある質問

C言語のマクロとは何ですか?

プリプロセッサがコンパイル前にソースへ置き換える、名前の付いたテキスト片のことです。#define MAX 100 は以降のすべての MAX100 にします。#define SQUARE(x) ((x) * (x)) は引数を取り、それをパターンへ埋め込みます。マクロには型がなく、スコープの規則にも従いません - 単なるテキスト置換です。

C言語のマクロにはなぜあれほど括弧が必要なのですか?

本体が周囲のコードへ貼り付けられ、その全体としてパースされるからです。#define SQUARE(x) x * xSQUARE(2 + 3)2 + 3 * 2 + 3 にし、結果は 25 ではなく 11 になります。すべての引数と本体全体を包む - ((x) * (x)) - ことで、周囲が何であろうと置換後のテキストが意図したとおりにまとまります。

C言語のマクロと関数の違いは何ですか?

関数は一度だけコンパイルされ、型チェックされ、各引数をちょうど 1 回評価します。マクロは使用箇所ごとに貼り付けられ、何もチェックせず、引数を複数回評価することがあります - なので MAX(i++, j)i を 2 回インクリメントしうるのです。関数(および定数には const/enum)を優先し、__LINE__ の取得やコード生成など、関数にはできないことにマクロを使いましょう。

複数行のマクロはなぜ do { ... } while (0) で包むのですか?

1 つの文のように振る舞わせるためです。むき出しの { ... } の本体は、if/else の中でセミコロンが続くと壊れます(セミコロンが if を早く終わらせるため)。また単なる文の並びは、波かっこのない if の中で壊れます。do { ... } while (0) は、末尾のセミコロンを受け付け、本体をちょうど 1 回実行する 1 つの文です。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める