核心 API(<stdarg.h>)
| 宏/類型 | 作用 |
| va_list | 參數列表指針類型 |
| va_start(ap, last_fixed) | 初始化 ap 指向第一個可變參數 |
| va_arg(ap, type) | 取出下一個參數,按 type 大小移動 ap |
| va_end(ap) | 清理 ap(某些平台需調用) |
| va_copy(dest, src) | C99:複製參數列表,避免二次遍歷失效 |
基礎示例:求和函數
#include
#include
int sum(int count, ...) {
va_list ap;
va_start(ap, count);
int total = 0;
for (int i = 0; i < count; i++)
total += va_arg(ap, int);
va_end(ap);
return total;
}
int main() {
printf("10+20+30 = %d\n", sum(3, 10, 20, 30));
printf("4+20+25+30 = %d\n", sum(4, 4, 20, 25, 30));
return 0;
}
進階封裝:類 printf 格式化輸出
void my_log(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap); // 標準庫 vprintf 直接消費 va_list
va_end(ap);
}
/* 帶級別、文件、行號 */
void log_ex(int level, const char *file, int line, const char *fmt, ...) {
static const char *prefix[] = {"[DEBUG]", "[INFO]", "[WARN]", "[ERROR]"};
fprintf(stderr, "%s %s:%d ", prefix[level], file, line);
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
}
#define LOG_INFO(fmt, ...) log_ex(1, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
關鍵注意事項
| 陷阱 | 說明 | 對策 |
| 無參數個數信息 | va_arg 不知道何時停止 | 約定:首參數為 count / 格式串 / 哨兵值 |
| 類型不匹配 | va_arg(int) 實傳 double → UB | 嚴格按聲明類型傳遞,或用 _Generic (C11) |
| 二次遍歷失效 | va_list 可能為一次性迭代器 | 用 va_copy 複製副本 |
| 默認參數提升 | float→double, char/short→int | va_arg 取提升後類型 |
| 可變參數不能為引用/非 POD | C++ 類對象傳值語義 | C++ 用 variadic templates 替代 |
實戰:簡易錯誤報告宏
#define ERR_RETURN(cond, fmt, ...) do { \
if (cond) { \
fprintf(stderr, "[ERR] %s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
return -1; \
} \
} while(0)
/* 使用 */
int read_file(const char *path) {
FILE *fp = fopen(path, "r");
ERR_RETURN(!fp, "Cannot open %s", path);
/* ... */
return 0;
}
總結
va_list 系列是 C 語言實現可變參數的基石。掌握「初始化 → 逐個提取 → 清理」流程,理解默認參數提升、哨兵約定、va_copy,即可編寫健壯的日志、格式化、錯誤處理等通用工具。