這是 C 語言中廣泛使用的編譯期技巧,利用 do-while 循環恰好執行一次的特性,構建一個獨立的語句塊作用域,解決宏展開、錯誤處理、代碼組織等多類問題。
問題:函數開頭分配多個資源,中途失敗需按逆序釋放。
傳統 goto 方案:標籤跳轉,代碼跳躍難追蹤。
do-while(0) 方案:用 break 替代 goto,結構化清理:
int func(void) {
int ret = -1;
Resource *r1 = NULL, *r2 = NULL, *r3 = NULL;
do {
r1 = allocate_res1();
if (!r1) break;
r2 = allocate_res2();
if (!r2) break;
r3 = allocate_res3();
if (!r3) break;
// 核心業務邏輯
if (do_critical_work() != 0) break;
ret = 0; // 成功
} while (0);
// 統一清理區 (逆序釋放)
if (r3) release_res3(r3);
if (r2) release_res2(r2);
if (r1) release_res1(r1);
return ret;
}
優勢:無 goto、單入口單出口、清理邏輯集中、易維護擴展。
// 空宏直接定義會產生 "empty declaration" 警告
#define EMPTY_MACRO()
// 正確寫法
#define EMPTY_MACRO() do { } while(0)
編譯器看到合法語句塊,不再警告。Linux Kernel 大量使用。
錯誤宏:
#define SWAP(x, y) { int tmp = x; x = y; y = tmp; }
// 使用時
if (a > b)
SWAP(a, b); // 展開為 if (a > b) { ... }; ← 分號結束 if
else
do_something(); // 錯誤!else 無匹配 if
正確宏:
#define SWAP(x, y) do { int tmp = x; x = y; y = tmp; } while(0)
// 展開後
if (a > b)
do { ... } while(0); // 整體是一條語句
else
do_something(); // 正確匹配
關鍵:do-while(0) 使宏展開後成為單一複合語句,可安全跟在 if/else/for/while 後且不改變控制流。
#define CHECK(cond) do { \
if (!(cond)) { \
log_error("Check failed: %s", #cond); \
return -1; \
} \
} while(0)
// 使用
CHECK(ptr != NULL);
CHECK(size > 0);
多語句、含返回、含花括號,do-while(0) 完美封裝。
| 構造 | 缺點 |
|---|---|
while(0) { ... } | 某些編譯器警告 "condition is constant" |
if(1) { ... } else | 需配套 else,嵌套時易產生 dangling-else 問題 |
{ ... } 裸塊 | 不能跟在 if/else 後作為單語句 (需分號,導致空語句) |
do { ... } while(0) | 完美:單語句、無警告、無 dangling-else、可 break、C89/C99/C11 全支援 |
#define LOG(fmt, ...) do { \
fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
} while(0)
LOG("Value: %d", x);
do { ... } while(0) 是 C 語言宏編程的瑞士軍刀:
掌握此慣用法,是寫出健壯、可維護 C 宏與庫代碼的必備技能。