__VA_ARGS__ 只能用於宏定義的替換列表中,配合 ... 表示可變參數。MSVC 2005+、GCC 3.0+、Clang 全支援。
| 模式 | 定義示例 | 調用示例 | 展開結果 |
|---|---|---|---|
| 條件編譯日誌 | #define LOG(...) printf(__VA_ARGS__) | LOG("err=%d\n", errno); | printf("err=%d\n", errno); |
| 帶前綴的條件日誌 | #define LOG(fmt, ...) printf("[LOG] " fmt, ##__VA_ARGS__) | LOG("connect %s\n", ip); | printf("[LOG] connect %s\n", ip); |
| 斷言宏 | #define ASSERT(cond, ...) if(!(cond)) { fprintf(stderr, __VA_ARGS__); abort(); } | ASSERT(ptr, "ptr null at %s:%d\n", __FILE__, __LINE__); | 條件失敗時打印並中止 |
// 標準 C99 寫法(GCC/Clang 支援)
#define LOG(fmt, ...) printf(fmt, ##__VA_ARGS__)
// MSVC 2015 前不支援 ##__VA_ARGS__,需寫兩個宏
#define LOG_1(fmt) printf(fmt)
#define LOG_2(fmt, ...) printf(fmt, __VA_ARGS__)
// 使用時需手動區分有無可變參數
關鍵差異:##__VA_ARGS__ 在 __VA_ARGS__ 為空時「吃掉」前面的逗號,避免 printf("fmt", ) 語法錯誤。現代 MSVC (2015+) 已支援標準寫法。
#define LOG_LEVEL_DEBUG 0
#define LOG_LEVEL_INFO 1
#define LOG_LEVEL_WARN 2
#define LOG_LEVEL_ERROR 3
#define CURRENT_LOG_LEVEL LOG_LEVEL_INFO
#define LOG_DEBUG(fmt, ...) \
do { if (CURRENT_LOG_LEVEL <= LOG_LEVEL_DEBUG) \
printf("[DEBUG][%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); } while(0)
#define LOG_INFO(fmt, ...) \
do { if (CURRENT_LOG_LEVEL <= LOG_LEVEL_INFO) \
printf("[INFO ][%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); } while(0)
// 使用
LOG_DEBUG("conn fd=%d", fd); // 當前級別 INFO,不輸出
LOG_INFO("user %s login", name); // 輸出: [INFO ][main.c:20] user admin login
do-while(0) 技巧:使宏在語法上表現為單條語句,安全用於 if/else 分支中。
#define NARG(...) NARG_(__VA_ARGS__, 5,4,3,2,1)
#define NARG_(_1,_2,_3,_4,_5,N,...) N
#define MACRO(...) MACRO_##NARG(__VA_ARGS__)(__VA_ARGS__)
#define MACRO_1(a) handle1(a)
#define MACRO_2(a,b) handle2(a,b)
#define MACRO_3(a,b,c) handle3(a,b,c)
MACRO(x); // 展開 handle1(x)
MACRO(x,y); // 展開 handle2(x,y)
利用宏參數計數實現偽函數重載,C 語言元編程經典技法。
LOG("msg") 時 __VA_ARGS__ 為空,必須用 ##__VA_ARGS__ 消除逗號,否則 C99 標準下行為未定義(GCC/Clang 兼容,MSVC 舊版報錯)。MACRO(func()) 若展開多次使用參數,func() 會被多次調用。gcc -E 預處理後查看展開結果。__VA_ARGS__ 讓 C 宏具備了「函數式可變參數」能力,配合 ## 消除逗號、do-while(0) 語句化、__FILE__/__LINE__ 定位,能構建極具表達力的日誌、斷言、追蹤、序列化框架。**原則:宏只做語法層面的代碼生成,業務邏輯盡量下沉到 inline 函數/模板。**