C 語言可變參數函數(如 `printf`)實現原理常被忽視。本文對比兩種做法:手動棧帧解析(教學用、非標準、不可移植)與 `
#include <stdio.h>
#include <assert.h>
// 假設 cdecl 呼叫約定、參數右至左入棧、int 為 4 字節
long sum(int num, ...) {
assert(num > 0);
int *p = &num + 1; // 緊鄰 num 之後的棧位置
long ret = 0;
while (num--) ret += *p++;
return ret;
}
缺點:依賴特定 ABI(調用約定、棧增長方向、參數對齊、寄存器傳參),x64 / ARM / 非 cdecl 環境全部失效,**嚴禁生產代碼使用**。
| 宏 | 用途 |
|---|---|
| `va_list ap` | 定義參數列表游標 |
| `va_start(ap, last_fixed)` | 初始化游標指向第一個可變參數 |
| `va_arg(ap, type)` | 取出當前參數並推進游標(需知類型) |
| `va_end(ap)` | 清理(某些平台需釋放資源) |
| `va_copy(dest, src)` (C99) | 複製游標(多次遍歷必須) |
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <tchar.h>
void MyPrintf(const TCHAR *fmt, ...) {
va_list ap;
va_start(ap, fmt);
// 1. 計算所需緩衝區大小(_vscprintf 不含結尾 \0)
int len = _vsctprintf(fmt, ap) + 1;
if (len <= 0) { va_end(ap); return; }
// 2. 分配緩衝區
TCHAR *buf = (TCHAR *)malloc(len * sizeof(TCHAR));
if (!buf) { va_end(ap); return; }
// 3. 重新遍歷參數列表格式化(需 va_copy 或 va_start 再次)
va_end(ap);
va_start(ap, fmt);
_vstprintf_s(buf, len, fmt, ap);
// 4. 輸出並釋放
_putts(buf);
free(buf);
va_end(ap);
}
int _tmain() {
long s = sum(3, 10, 110, 200); // 320
MyPrintf(_T("%d %c %d"), 123, _T('<'), 456);
MyPrintf(_T("%s"), _T("This is a string"));
getchar();
return 0;
}
| 維度 | 手動棧解析 | C++ 變參模板 | |
|---|---|---|---|
| 可移植性 | 極差 | 標準保證 | 標準 C++11 |
| 類型安全 | 無 | 程序員自負 | 編譯器保證 |
| 效能 | 極快(無函數調用) | 極快(宏展開) | 零開銷(內聯) |
| 適用場景 | 教學/嵌入式極限優化 | C 語言通用可變參數 | 現代 C++ 新代碼 |