在 C 語言開發中,變長參數函數是實現靈活介面的關鍵技術。標準庫中的 printf、scanf 等函數皆採用此機制。本文將系統介紹 va_list 的核心機制、常見陷阱與進階應用模式。
標準頭文件 <stdarg.h> 提供四個核心巨集:
va_list:參數列表指標型別va_start(ap, last_fixed):初始化參數列表va_arg(ap, type):按型別提取下一個參數va_end(ap):清理參數列表void vltest(int i, float k, ...) {
va_list vl;
va_start(vl, k); // 以最後一個固定參數為起點
int j = va_arg(vl, int);
double m = va_arg(vl, double);
unsigned long n = va_arg(vl, unsigned long);
va_end(vl);
printf("i=%d, k=%.0f, j=%d, m=%lf, n=%lu\n", i, k, j, m, n);
}
上述機制要求呼叫端與被呼叫端對參數序列有完全一致的理解。若需實現類似 printf 的「智慧解析」,必須自行設計協定(如格式化字串)。
#include <stdarg.h>
#include <assert.h>
void my_printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
for (; *fmt; ++fmt) {
if (*fmt != '%') { putchar(*fmt); continue; }
if (*++fmt == '\0') { assert(0); break; }
switch (*fmt) {
case '%': putchar('%'); break;
case 'd': {
int i = va_arg(ap, int);
printf("%d", i);
break;
}
case 'c': {
// 關鍵修正:char 會被提升為 int
int c = va_arg(ap, int);
printf("%c", c);
break;
}
}
}
va_end(ap);
}
根據 C 標準,可變參數在傳遞時會發生預設引數提升:
| 原始型別 | 提升後型別 |
|---|---|
| float | double |
| char / signed char / unsigned char | int |
| short / unsigned short | int |
| 若 int 無法容納則提升為 unsigned int |
因此 va_arg(ap, char)、va_arg(ap, float) 等用法均為未定義行為,必須改為對應的提升型別並手動轉換:
// 正確寫法
char c = (char)va_arg(ap, int);
short s = (short)va_arg(ap, int);
float f = (float)va_arg(ap, double);
現代編譯器(如 Clang、GCC、MSVC)在偵測到非法型別時會發出警告,建議啟用 -Wvarargs 或 /Wall 以提早發現問題。
va_start/arg/end 三部曲char、short、float_Generic 或 C++ 變長模板