この記事は「渡部 Advent Calendar 2025」の15日目の記事です。
C のコードを読んでいると、こんなマクロに遭遇することがあります。
#define FOO(x) do { \ bar(x); \ baz(x); \ } while (0)
「えっ、while(0) って絶対 1 回で終わるじゃん。なにこれ?」となりがちですが、これは “複数文マクロを安全に書くための定番イディオム” です。結論から言うと、do { ... } while(0) で包むことで、マクロを 1 文として扱える ようになり、if 文などと組み合わせたときの事故を防げます。
事故る例:do { ... } while(0) が無いと何が起きる?
例えば、ログを吐く簡単なマクロを作ったとします。
#define LOG_ERROR(msg) \ fprintf(stderr, "ERROR: %s\n", msg); \ fflush(stderr);
これを if の中で使うと……。
if (cond) LOG_ERROR("bad"); else do_something();
展開後はこうなります(イメージ):
if (cond) fprintf(stderr, "ERROR: %s\n", "bad"); fflush(stderr); else do_something();
else の対応先が壊れて コンパイルエラー になったり、(別の形だと)意図しない挙動になったりします。原因は単純で、マクロが 複数文に展開されるのに、呼び出し側は 1 文だと思って書いている からです。
解決:do { ... } while(0) で「1 文化」する
同じマクロをこう書き換えます。
#define LOG_ERROR(msg) do { \ fprintf(stderr, "ERROR: %s\n", (msg)); \ fflush(stderr); \ } while (0)
この状態で先ほどと同じコードを書いても、
if (cond) LOG_ERROR("bad"); else do_something();
展開後はこうなります:
if (cond) do { fprintf(stderr, "ERROR: %s\n", "bad"); fflush(stderr); } while (0); else do_something();
マクロ全体が 1 文(1 ステートメント) になっているので、if / else の構造が崩れません。
じゃあ { ... } だけでよくない?
「{ ... } でブロックにすれば良くない?」と思うかもしれません。
#define LOG_ERROR(msg) { \ fprintf(stderr, "ERROR: %s\n", (msg)); \ fflush(stderr); \ }
でもこれは “式や文としての扱い” が微妙 で、呼び出し側が
if (cond) LOG_ERROR("bad"); else do_something();
みたいに書くと、処理系や状況によっては解釈が崩れたり(特に「マクロは 1 文」という前提が欲しい場面で)トラブルの元になります。do { ... } while(0) は 末尾にセミコロンを書ける ことも含めて「いつでも 1 文として振る舞う」ことが大きいです。
まとめ
- do { ... } while(0) は 複数文マクロを 1 文として扱う ための定番
- if (cond) MACRO(); else ... みたいな構造で 事故らない
ただし、マクロなので 引数の副作用 など別の罠は残る。
関係ないのですがこの記事書くために久々にこの辺の技術ワードでググったのですが昔読んでいたブログがたくさんヒットして懐かしい気持ちになりました...