printf 等函數如何實現參數個數、類型可變?本文從使用步驟、編譯器宏展開、編程陷阱三個維度全景解析。
void simple_va_fun(int i, ...) {
va_list arg_ptr;
int j = 0;
va_start(arg_ptr, i); // 1. 初始化
j = va_arg(arg_ptr, int); // 2. 取參數
va_end(arg_ptr); // 3. 結束
printf("%d %d\n", i, j);
}
調用示例:
simple_va_fun(100); → 輸出 100 -123456789 (隨機值)simple_va_fun(100, 200); → 100 200simple_va_fun(100, 200, 300); → 100 200 (多餘參數被忽略)typedef char * va_list;
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define va_start(ap, v) (ap = (va_list)&v + _INTSIZEOF(v))
#define va_arg(ap, t) (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define va_end(ap) (ap = (va_list)0)
棧分佈圖解:
高地址 |-----------------------------|
| 函數返回地址 |
|-----------------------------|
| ... |
|-----------------------------| ← va_start 後 ap 指向此處
| 第 n 個參數 (第一個可變參數) |
|-----------------------------|
| 第 n-1 個參數 (最後固定參數) | ← &v
低地址 |-----------------------------|
va_start:ap = &v + 對齊後大小 → 指向第一個可變參數va_arg:ap += size(t) 後取 ap - size(t) 處的值va_end:置空指針,防止誤用printf 解析格式串)va_start 取地址,參數不能是 register 或數組/函數類型va_start 參數個數不同,stdarg.h (ANSI) 與 varargs.h (System V) 不兼容,現代代碼統一用 ANSIC++ 應優先使用函數重載、模板、std::initializer_list、variadic templates (C++11) 實現類似功能,類型安全且編譯期可檢查。
可變參數本質是棧指針運算,va_* 宏封裝了平台相關細節。掌握其原理有助於調試棧損壞、理解調用約定,但實際工程中能不用盡量不用。