C 語言透過 `
#include
// 至少一個固定參數,後接 ...
void func(int fixed1, int fixed2, ...) {
// 1. 宣告參數列表指標
va_list ap;
// 2. 初始化:指向第一個可變參數
va_start(ap, fixed2); // 傳入最後一個固定參數
// 3. 循環提取參數
while (/* 有結束條件 */) {
int val = va_arg(ap, int); // 指定型別提取
// 處理 val...
}
// 4. 清理
va_end(ap);
}
// 以 "ListEnd" 為結束標記
char* AppendString(const char* first, ...) {
va_list ap;
va_start(ap, first);
size_t total_len = strlen(first);
const char* str = first;
// 第一輪:計算總長度
while ((str = va_arg(ap, const char*)) != NULL) {
if (strcmp(str, "ListEnd") == 0) break;
total_len += strlen(str);
}
va_end(ap);
// 配置記憶體
char* result = malloc(total_len + 1);
if (!result) return NULL;
// 第二輪:複製內容
va_start(ap, first);
strcpy(result, first);
char* dst = result + strlen(first);
while ((str = va_arg(ap, const char*)) != NULL) {
if (strcmp(str, "ListEnd") == 0) break;
strcpy(dst, str);
dst += strlen(str);
}
*dst = '\0';
va_end(ap);
return result;
}
// 使用
char* s = AppendString("Hello", " ", "World", "ListEnd", NULL);
// s = "Hello World"
free(s);
呼叫:TestFunc(a, b, c, d, e) // e 為結束標記 棧幀 (高位址 → 低位址): +------------------+ | e | ← 最後參數 +------------------+ | d | +------------------+ | c | ← 第一個可變參數 +------------------+ | b | ← 最後一個固定參數 (va_start 參考點) +------------------+ | a | ← 第一個固定參數 +------------------+ | 返回位址 | +------------------+ | 舊 EBP | ← EBP +------------------+ | 區域變數... | +------------------+ ← ESP
// 計算型別在堆疊佔用大小 (含對齊)
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
// va_list 定義 (x86)
typedef char* va_list;
// 1. 初始化:ap 指向第一個可變參數
#define va_start(ap, v) (ap = (va_list)&v + _INTSIZEOF(v))
// 2. 提取:取得當前參數,ap 前進到下一個
#define va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
// 3. 清理:ap 歸零
#define va_end(ap) (ap = (va_list)0)
// 4. 複製 (C99)
#define va_copy(dest, src) (dest = src)
// 原始:*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))
// 等價於兩步:
ap += _INTSIZEOF(t); // 1. ap 前進到下一個參數位置
return *(t*)(ap - _INTSIZEOF(t)); // 2. 回溯取得當前參數值
| 隱患 | 成因 | 防範策略 |
|---|---|---|
| 型別檢查缺失 | `va_arg` 強制轉型,傳入 `float` 卻用 `va_arg(ap, int)` 取值 → 未定義行為 | 約定格式字串 (如 `printf`)、傳遞型別標籤、改用 C++ 變參模板 |
| 結束條件缺失 | 無結束標記 → 繼續讀取堆疊垃圾資料 → 存取違規/無限迴圈 | **強制要求** 呼叫端提供結束標記 (如 `NULL`、負數、特定字串、計數參數) |
void func(const char* fmt, ...) {
va_list ap1, ap2;
va_start(ap1, fmt);
va_copy(ap2, ap1); // 複製一份供第二次遍歷
// 第一遍:計算長度
// 第二遍:格式化輸出 (vsnprintf 等)
va_end(ap1);
va_end(ap2); // 每個 va_start/va_copy 必配對 va_end
}
int vspf(char* buf, size_t bufsize, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
int cnt = vsnprintf(buf, bufsize, fmt, ap);
va_end(ap);
return cnt;
}
// 使用
char buffer[80];
vspf(buffer, sizeof(buffer), "%d %f %s", 30, 90.0, "abc");
printf("%s\n", buffer); // "30 90.000000 abc"
可變參數機制是 C 語言靈活性的體現,但也帶來型別安全與記憶體安全的雙重挑戰。理解其底層堆疊機制與對齊規則,能幫助你在必須使用 C 介面時正確實現,並在 C++ 專案中果斷選擇現代型別安全替代方案。