現代 C (C11/C17/C23)

可變引數巨集(variadic macro,__VA_ARGS__ / __VA_OPT__)

/ var-ee-AD-ik /

一般巨集接收固定數量的引數。但有時你想要一個巨集,能轉發它收到的任意數量引數——想想包裝 printf 並把你輸入的一切都傳下去的日誌巨集 LOG(fmt, ...)。可變引數巨集是參數列尾端帶有 ... 的類函式巨集,而本體中的 __VA_ARGS__ 代表被該 ... 匹配的所有引數。

你寫 #define LOG(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__),__VA_ARGS__ 會展開成以逗號分隔的額外引數。經典的問題是空引數情況:若有人寫 LOG("hi") 而沒有額外引數,舊程式碼會留下一個多餘的逗號,變成 fprintf(stderr, "hi", )。C23 用 __VA_OPT__(內容) 修正了它:__VA_OPT__ 內的內容「只」在可變引數非空時才輸出,所以 __VA_OPT__(,) 恰好在需要時補上逗號,否則就移除它。(在 C23 之前,編譯器提供非標準的技巧,例如 GNU 的 ', ##__VA_ARGS__' 擴充。)

它對引數數量會變化的日誌、斷言與包裝巨集很有用。兩個注意點:巨集做的是文字替換,所以含有未被括號包住之逗號的引數可能被誤算(單一的 ((a,b)) 可保護逗號),而且像所有巨集一樣沒有型別檢查,若本體重複使用某引數,它可能被求值不只一次——請讓可變引數巨集的引數不含副作用。

// C23:__VA_OPT__ 只在 fmt 後面有引數時才補上逗號 #define LOG(fmt, ...) fprintf(stderr, fmt __VA_OPT__(,) __VA_ARGS__) LOG("starting\n"); // -> fprintf(stderr, "starting\n") LOG("x = %d\n", 42); // -> fprintf(stderr, "x = %d\n", 42)

__VA_ARGS__ 轉發額外引數;__VA_OPT__(,)(C23)只在有額外引數時才輸出分隔逗號。

巨集是文字替換且未經檢查:含未加括號逗號的引數可能被誤算,而重複使用某引數的巨集可能讓副作用被求值不只一次。

又稱
variable-argument macroVA_ARGS macro可變引數巨集