stdarg.h 提供的 va_list/va_start/va_arg/va_end 是 C 語言實現 printf/scanf 等可變參函數的標準機制。本文從棧帧原理到宏定義源碼,徹底講透「怎麼用、為什麼這樣用、易錯在哪」。
typedef char *va_list; // 本質是字節指針,指向當前參數 // 使 ap 指向第一個可變參數 #define va_start(ap, last) (ap = (va_list)&(last) + _INTSIZEOF(last)) // 取出當前參數並移動 ap 到下一個 #define va_arg(ap, type) (*(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type))) // 結束:置空指針 #define va_end(ap) (ap = (va_list)0) // 對齊輔助宏:向上取整到 int 倍數 #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#includedouble average(int count, ...) { // 至少一個固定參數 va_list ap; // 1. 定義遍歷指針 va_start(ap, count); // 2. 初始化:指向第一個可變參 double sum = 0; for (int i = 0; i < count; i++) sum += va_arg(ap, double); // 3. 依次取出(必須指定類型) va_end(ap); // 4. 收尾:置空,防野指針 return sum / count; }
void my_printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap); // 標準庫已封裝好 vprintf/vfprintf/vsprintf
va_end(ap);
}
int sum(int first, ...) { // 約定:以 0 結束
va_list ap;
va_start(ap, first);
int s = 0, val = first;
while (val != 0) {
s += val;
val = va_arg(ap, int);
}
va_end(ap);
return s;
}
// 調用:sum(1, 2, 3, 4, 0); // 必須以 0 結尾
// 通用查找:滿足 predicate 的調用 action
void find_and_do(int *arr, int n,
int (*predicate)(int, va_list), // 可變參判斷
void (*action)(int, va_list), // 可變參動作
...) {
va_list ap;
for (int i = 0; i < n; i++) {
va_start(ap, n); // 每次重新開始(簡化演示)
if (predicate(arr[i], ap))
action(arr[i], ap);
va_end(ap);
}
}
va_arg(ap, int) 傳 double 會導致棧指針錯位,後續全亂va_copy(dest, src) (C99) 複製副本float 會自動提升為 double,取時必須用 va_arg(ap, double)可變參數本質是**程序員手動解析棧帧**。掌握 va_start 定位起點、va_arg 按類型步進、va_end 清理現場,並建立**參數協議**(個數、類型、終止),才能安全駕馭這把「雙刃劍」。