C 語言通過 <stdarg.h> 提供可變參數機制,支撐 printf/scanf 等核心庫函數。本文系統講解 va_list 四大宏用法、參數提升規則、類型安全陷阱及自定義格式化函數實現。
#include <stdarg.h>
void vltest(int i, float k, ...) {
va_list vl; // 1. 聲明變量
va_start(vl, k); // 2. 初始化(最後一個固定參數)
int j = va_arg(vl, int); // 3. 依次獲取參數
double m = va_arg(vl, double);
unsigned long n = va_arg(vl, unsigned long);
va_end(vl); // 4. 清理(必須調用)
printf("i=%d, k=%.f, j=%d, m=%lf, n=%lu\n", i, k, j, m, n);
}
調用方必須約定協議(如格式字符串),這正是 printf 的設計精髓。
void my_printf(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
for (; *fmt; ++fmt) {
if (*fmt != '%') { putchar(*fmt); continue; }
++fmt; // 檢查格式符
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); // 不能用 char
printf("%c", c);
break;
}
}
}
va_end(ap);
}
C 標準規定可變參數經歷默認實參提升:
| 原始類型 | 提升後類型 | va_arg 正確類型 |
|---|---|---|
float | double | double |
char/short (signed/unsigned) | int | int |
無法用 va_arg 直接獲取 | char、short、float 及其變體 | |
// char 參數
int c = va_arg(ap, int); // 獲取提升後的 int
printf("%c", (char)c); // 轉回 char 輸出
// short 參數
short s = (short)va_arg(ap, int);
// float 參數
float f = (float)va_arg(ap, double);
va_list 供多次遍歷NULL 結束字符串列表)_Generic 實現編譯期檢查vprintf 族函數而非手寫解析va_arg 類型與提升後類型一致va_end