do { ... } while(0) 慣用法在 Linux 內核等大型 C 專案中,常見如下宏定義模式:
#define DUMP_WRITE(addr, nr) \
do { \
memcpy(bufp, addr, nr); \
buf += nr; \
} while(0)
{ ... } 或直接多語句?#define MACRO stmt1; stmt2; 若用於 if (cond) MACRO; 僅 stmt1 受 if 控制,stmt2 無條件執行 → 邏輯錯誤。{ ... }:#define MACRO { stmt1; stmt2; } 用於 if (cond) MACRO; else ... 時,分號導致 if 提前結束,else 無匹配 if → 語法錯誤。do { ... } while(0):構成單一語句,配合結尾分號,完美適配 if-else、循環等所有語境,且編譯器會優化掉無意義循環,零開銷。為提高數據結構通用性,可用宏對結構體進行操作。例:雙向鏈表頭初始化:
#define INIT_LIST_HEAD(ptr) \
do { \
(ptr)->next = (ptr); \
(ptr)->prev = (ptr); \
} while(0)
同樣利用 do-while(0) 確保語句完整性。現代 C 也可用 inline 函數替代,兼具類型安全與效能。
container_of 核心原理Linux 內核廣泛使用的 container_of 宏,能從結構體任意成員地址反推結構體起始地址:
#define get_struct_addr_from_member_addr(p, stru, m) \
( (stru*) ( (char*)p - (char*)&(((stru*)0)->m) ) )
(stru*)0:將地址 0 強轉為 stru*,構造一個「假想結構體」位於地址 0。((stru*)0)->m:訪問該假想結構體的成員 m,其地址即為 m 在結構體中的偏移量(以位元組為單位)。&(((stru*)0)->m):取得該偏移量地址(數值等於偏移量)。(char*)p - (char*)&(((stru*)0)->m):成員實際地址 p 減去偏移量,得出結構體實際首地址。強轉為 char* 確保以位元組為單位運算。stru*,得到結構體指標。地址 0 為 NULL,不會被合法映射,僅作為編譯期計算偏移量的基準,不產生實際內存訪問。編譯器根據結構體定義直接計算成員偏移,無運行時開銷。
list_head* 反推宿主結構體。do { ... } while(0) 避免語法陷阱。inline 函數實現類型無關操作。container_of 技巧利用零地址強轉計算偏移,是 C 語言實現面向對象、內核級數據結構的基石。