C 語言不支援函數重載,`printf`、`scanf` 等函數需接受不定數量、不定類型參數。解決方案:調用約定 + 棧幀布局 + stdarg.h 抽象。
以 x86 cdecl 為例(參數從右向左壓棧,調用者清棧):
高地址 +------------------+ | 可選參數 N | | ... | | 可選參數 2 | | 可選參數 1 | <-- va_start 定位此處 | 固定參數 k | | ... | | 固定參數 1 | | 返回地址 | | 舊 EBP | <-- EBP +------------------+ 低地址
固定參數地址相對 EBP 固定,可選參數通過「最後一個固定參數地址 + 其大小」定位首個可選參數,再按類型大小逐個訪問。
| 宏 | 功能 | 典型實現邏輯 |
|---|---|---|
| `va_list` | 參數列表指針類型 | `typedef char *va_list;` |
| `va_start(ap, last)` | 初始化 `ap` 指向首個可選參數 | `ap = (va_list)&last + _INTSIZEOF(last)` |
| `va_arg(ap, type)` | 取得當前參數值,並推進 `ap` 到下一個 | `*(type*)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type))` |
| `va_end(ap)` | 清理(置 NULL,某些平台需釋放資源) | `ap = (va_list)0` |
| `va_copy(dest, src)` | C99:複製參數列表(多次遍歷必須用) | 逐字節複製或指針賦值 |
其中 `_INTSIZEOF(n) = ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))` 實現按 `int` 大小對齊。
#include <stdarg.h>
#include <stdio.h>
// 至少一個固定參數,作為 va_start 參考
void log_msg(const char *prefix, int level, ...) {
va_list ap;
va_start(ap, level); // last 固定參數
printf("[%s] ", prefix);
// 遍歷:約定以負數結束,或由 format 字符串決定數量
int val;
while ((val = va_arg(ap, int)) >= 0) {
printf("%d ", val);
}
printf("\n");
va_end(ap);
}
int main(void) {
log_msg("DEBUG", 1, 10, 20, 30, -1); // 輸出: [DEBUG] 10 20 30
return 0;
}
| 策略 | 適用場景 | 示例 |
|---|---|---|
| 哨兵值 | 同類型參數,有明確無效值 | `execl(path, arg0, arg1, ..., NULL)` |
| 計數參數 | 首參數給出數量 | `void foo(int n, ...)` |
| 格式字符串 | 類型不固定,由 format 解析 | `printf("%d %s", 1, "hi")` |
將 `va_list` 傳遞給標準庫格式化函數,實現「包裝 printf」:
void my_printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap); // 核心:vprintf 接受 va_list
va_end(ap);
}
// 進階:同時輸出到文件和控制台
void log_both(FILE *fp, const char *fmt, ...) {
va_list ap, ap2;
va_start(ap, fmt);
va_copy(ap2, ap); // 複製一份給第二次使用
vprintf(fmt, ap);
vfprintf(fp, fmt, ap2);
va_end(ap);
va_end(ap2);
}
#define LOG(level, fmt, ...) \
do { \
if (g_log_level >= level) { \
fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
} \
} while(0)
// 使用
LOG(INFO, "Connection from %s:%d", ip, port);
LOG(DEBUG, "Packet size = %zu", len);