C99 引入的可變參數宏(Variadic Macros)與可變參數函數(Variadic Functions)是實現類 printf 功能的基石。本文系統整理 __VA_ARGS__、va_list 等核心機制與用法。
#define DEBUG(fmt, ...) printf("DEBUG: " fmt, __VA_ARGS__)
...:表示參數列表可變。__VA_ARGS__:預處理階段展開為實際傳入的可變參數序列(含逗號分隔)。#define DBG(fmt, ...) \
printf("MSG: " fmt " [File:%s, Line:%d, Func:%s]\n", \
__VA_ARGS__, __FILE__, __LINE__, __FUNCTION__)
int main() {
double x = 48, y = sqrt(x);
DBG("x=%g\n", x);
DBG("x=%.2f, y=%.4f\n", x, y);
return 0;
}
輸出:
MSG: x=48.000000 [File:main.c, Line:10, Func:main]
MSG: x=48.00, y=6.9282 [File:main.c, Line:11, Func:main]
__VA_ARGS__ 的舊編譯器替代方案#define DEBUG_BUFFER_MAX_LENGTH 1024
void printDebugMsg(const char* format, ...) {
char buffer[DEBUG_BUFFER_MAX_LENGTH + 1] = {0};
va_list arg;
va_start(arg, format);
vsnprintf(buffer, DEBUG_BUFFER_MAX_LENGTH, format, arg);
va_end(arg);
printf("%s", buffer);
}
使用 va_list + vsnprintf 實現類似功能,兼容 C89。
stdarg.h)int SqSum(int n1, ...) {
va_list arg_ptr;
int nSqsum = 0;
int n = n1;
va_start(arg_ptr, n1); // n1 是最後一個固定參數
while (n > 1) {
nSqsum += n * n;
n = va_arg(arg_ptr, int); // 獲取下一個 int 參數
}
va_end(arg_ptr);
return nSqsum;
}
int main() {
// 參數:計算 2^2 + 3^2 + 4^2 + 5^2,以 -1 結束(邏輯自定義)
int sum = SqSum(2, 3, 4, 5, -1);
printf("sqsum = %d\n", sum); // 54
return 0;
}
| 宏 | 作用 |
|---|---|
va_list | 定義指向可變參數列表的指針對象 |
va_start(ap, last_fixed) | 初始化 ap 指向第一個可變參數,last_fixed 為最後一個固定參數名 |
va_arg(ap, type) | 返回 ap 當前指向參數(類型 type),並推進 ap 到下一個 |
va_copy(dest, src) | 複製參數列表指針(C99),避免 va_arg 消耗原列表 |
va_end(ap) | 清理 ap,匹配每個 va_start/va_copy |
va_start 的參考點)。printf 通過格式字符串,execl 以 NULL 結尾)。va_arg 類型不匹配導致未定義行為。va_arg 按類型大小推進指針。| 場景 | 推薦 | 理由 |
|---|---|---|
| 調試日志、輕量包裝 | 可變參數宏 | 零開銷、編譯期展開、自動攜帶 __FILE__/__LINE__ |
| 複雜邏輯、類型安全、遞歸 | 可變參數函數 | 運行期執行、可調試、參數類型檢查(有限)、支持遞歸 |
| 需多次遍歷參數 | 函數 + va_copy | 宏無法多次遍歷 __VA_ARGS__ |
// 可變參數模板(類型安全、零開銷)
template
void log(const char* fmt, Args&&... args) {
// 配合 fmtlib 或 iostream 實現
}
// 折疊表達式(C++17)
template
void print(Args&&... args) {
(std::cout << ... << args) << '\n';
}
現代 C++ 推薦使用可變參數模板替代 C 風格可變參數,獲得編譯期類型安全與更好性能。
__VA_ARGS__ 是 C99 宏系統的關鍵擴展,配合 ## 可解決零參數時的多餘逗號問題(##__VA_ARGS__)。va_list 系列宏是 C 標準庫實現 printf/scanf 族函數的基石。va_arg 推進機制,有助於排查棧損壞類 Bug。fmt::format(C++20 std::format)。