C99 標準引入 __VA_ARGS__ 讓巨集支援可變參數,極大增強日誌、斷言、除錯巨集的靈活性。Visual C++ 2005+ 亦支援。
#define MACRO_NAME(fixed_args, ...) replacement_with_##__VA_ARGS__
// 關鍵點:
// 1. 必須至少一個固定參數
// 2. ... 表示可變參數
// 3. __VA_ARGS__ 在替換體中展開為實際參數列表
#include
#define CHECK1(x, ...) if (!(x)) { printf(__VA_ARGS__); }
#define CHECK2(x, ...) if ((x)) { printf(__VA_ARGS__); }
#define CHECK3(...) { printf(__VA_ARGS__); }
#define MACRO(s, ...) printf(s, __VA_ARGS__)
int main() {
CHECK1(0, "here %s %s %s", "are", "some", "varargs1(1)\n"); // 列印
CHECK1(1, "here %s %s %s", "are", "some", "varargs1(2)\n"); // 不列印
CHECK2(0, "here %s %s %s", "are", "some", "varargs2(3)\n"); // 不列印
CHECK2(1, "here %s %s %s", "are", "some", "varargs2(4)\n"); // 列印
CHECK3("here %s %s %s", "are", "some", "varargs3(5)\n"); // 總是列印
MACRO("hello, world\n"); // 正常
// MACRO("error\n", EMPTY); // 錯誤:需至少一個可變參數
}
here are some varargs1(1) here are some varargs2(4) here are some varargs3(5) hello, world
GCC/Clang 擴充支援 ##__VA_ARGS__ 自動移除前置逗號:
// 標準 C99:呼叫需至少一個參數
#define LOG(fmt, ...) printf(fmt, __VA_ARGS__)
// LOG("test"); // 錯誤:展開為 printf("test", ) 語法錯誤
// GCC/Clang 擴充:支援零參數
#define LOG(fmt, ...) printf(fmt, ##__VA_ARGS__)
// LOG("test"); // 正常:展開為 printf("test")
// 跨平台相容寫法
#if defined(__GNUC__) || defined(__clang__)
#define LOG(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define LOG(fmt, ...) printf(fmt, __VA_ARGS__) // MSVC 需至少一個參數
#endif
#ifdef DEBUG
#define DBG(fmt, ...) fprintf(stderr, "[DBG] %s:%d " fmt "\n", \
__FILE__, __LINE__, ##__VA_ARGS__)
#else
#define DBG(fmt, ...) ((void)0)
#endif
// 使用
DBG("user %s login, id=%d", username, uid);
#define ASSERT(cond, ...) \
do { \
if (!(cond)) { \
fprintf(stderr, "[ASSERT] %s:%d: " #cond " failed: ", \
__FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
abort(); \
} \
} while(0)
ASSERT(ptr != NULL, "ptr=%p, size=%zu", ptr, size);
#define VECTOR_PUSH(vec, ...) \
do { \
typeof((vec)->data) _vals[] = {__VA_ARGS__}; \
for (size_t _i = 0; _i < sizeof(_vals)/sizeof(_vals[0]); _i++) \
vector_push_back(vec, &_vals[_i]); \
} while(0)
VECTOR_PUSH(my_vec, 1, 2, 3, 4, 5);
| 問題 | 說明 | 解決 |
|---|---|---|
| 零參數展開 | C99 要求至少一個可變參數 | GCC 用 ##__VA_ARGS__,MSVC 需提供 dummy 參數 |
| 參數副作用 | 巨集多次展開參數 | 避免在 __VA_ARGS__ 中放 ++、函數呼叫 |
| 型別安全 | 巨集無型別檢查 | 配合 _Generic (C11) 或改用內聯函數 |
| 除錯困難 | 巨集展開後行號偏移 | 編譯器 -E 預處理輸出檢查 |
// C23 標準化解決零參數問題
#define LOG(fmt, ...) printf(fmt __VA_OPT__(,) __VA_ARGS__)
// 相當於:
// LOG("hello") → printf("hello")
// LOG("val=%d", x) → printf("val=%d", x)
_ 避免衝突gcc -E file.c 驗證展開結果