參數個數/類型不固定的函數。典型例:printf(const char *format, ...),...為佔位符。
printf("%d", i);
printf("%s", s);
printf("num=%d, str=%s", i, s);
#include
void simple_va_fun(int start, ...) {
va_list arg_ptr; // ① 定義va_list變量(指向參數棧地址)
int nArgValue = start; // 首個固定參數
int nArgCount = 0;
va_start(arg_ptr, start); // ② 初始化:以最後固定參數地址為基準
do {
++nArgCount;
printf("arg %d: %d\n", nArgCount, nArgValue);
nArgValue = va_arg(arg_ptr, int); // ③ 取下一參數:指針推進+類型解讀
} while (nArgValue != -1); // ④ 結束條件:約定哨兵值-1
va_end(arg_ptr); // 清理(某實現無操作,但規範要求)
}
| 宏 | 原型 | 作用 |
|---|---|---|
| va_start | void va_start(va_list ap, last_fixed_param); | 初始化ap指向首個可變參數 |
| va_arg | type va_arg(va_list ap, type); | 取當前參數值,推進ap到下一參數 |
| va_end | void va_end(va_list ap); | 清理ap(某ABI需棧指針恢復) |
... | arg3 | arg2 | arg1 | 返回地址 | 舊EBP | 局部變量 ...typedef char *va_list; — 指向當前可變參數的字節指針。
#define va_start(ap, last) (ap = (va_list)(&last + sizeof(last)))
// 取最後固定參數地址,跨過其大小,指向下一參數(首個可變參數)
#define va_arg(ap, type) \
(*(type*)((ap += sizeof(type)) - sizeof(type)))
// 1. ap += sizeof(type) 推進到下一參數起始
// 2. - sizeof(type) 回到當前參數起始
// 3. 強轉type*解引用取值
format字符串解析、哨兵值、計數參數float→double、char/short→int → va_arg(ap, float)錯誤,須用doublevariadic templates完美替代va_copy(dest, src) (C99) 複製副本| 模式 | 示例 | 優點 | 缺點 |
|---|---|---|---|
| 格式字符串 | printf("%d%s", i, s) | 靈活、標準 | 運行期解析開銷、類型不安全 |
| 哨兵值 | func(1,2,3,-1) | 簡單 | 值域受限、易誤觸發 |
| 計數參數 | func(3, 1,2,3) | 明確 | 冗餘、易不一致 |
| C++11可變參數模板 | template | 類型安全、編譯期展開、零開銷 | 需C++11、模板代碼膨脹 |
va_listva_list跨函數傳遞用va_copy或引用傳遞fmt庫/std::format (C++20),避免printf類型不安全可變參數是C語言通過棧佈局約定與指針算術實現的「受控不安全特性」。理解va_list機制有助診斷崩潰、編寫兼容層。現代C++以可變參數模板從語言級解決類型安全與性能雙重難題,是不可逆的演進方向。