C 語言通過 <stdarg.h> 支持參數數量可變的函數。核心三步驟:
va_start(ap, last_fixed):初始化參數列表遍歷器va_arg(ap, type):按類型提取下一個參數va_end(ap):清理遍歷器 (必須調用)va_list 是實現定義的類型,通常為 char * 或結構體。
#include <stdarg.h>
返回類型 函數名(固定參數, ...); // 省略號必須是最後一個參數
// 示例
int sum(int count, ...);
void log_msg(const char *fmt, ...);
ap 指向第一個可變參數last_fixed 是最後一個固定參數名 (省略號前的參數)va_arg 前調用type 的下一個參數值ap 指向後續參數float→double、char/short→intap,某些實現需釋放棧空間va_start 必須配對 va_endap 不可再用,需重新 va_start
#include <stdarg.h>
#include <stdio.h>
int sum(int count, ...) {
va_list ap;
int total = 0;
va_start(ap, count);
for (int i = 0; i < count; i++) {
total += va_arg(ap, int); // 假設所有參數為 int
}
va_end(ap);
return total;
}
int main() {
printf("sum(3, 10, 20, 30) = %d\n", sum(3, 10, 20, 30)); // 60
printf("sum(5, 1, 2, 3, 4, 5) = %d\n", sum(5, 1, 2, 3, 4, 5)); // 15
return 0;
}
#include <stdarg.h>
#include <stdio.h>
void my_printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
while (*fmt) {
if (*fmt == '%') {
fmt++;
switch (*fmt) {
case 'd': {
int val = va_arg(ap, int);
printf("%d", val);
break;
}
case 's': {
char *val = va_arg(ap, char *);
printf("%s", val);
break;
}
case 'f': {
double val = va_arg(ap, double); // float 提升為 double
printf("%f", val);
break;
}
case 'c': {
int val = va_arg(ap, int); // char 提升為 int
putchar(val);
break;
}
default:
putchar(*fmt);
}
} else {
putchar(*fmt);
}
fmt++;
}
va_end(ap);
}
int main() {
my_printf("整數: %d, 字符串: %s, 浮點: %f, 字符: %c\n", 42, "hello", 3.14, 'X');
return 0;
}
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
void log_message(const char *level, const char *fmt, ...) {
va_list ap;
time_t now = time(NULL);
char *ts = ctime(&now);
ts[strlen(ts)-1] = '\0'; // 去除換行
printf("[%s] %s: ", ts, level);
va_start(ap, fmt);
vprintf(fmt, ap); // 直接傳遞 va_list
va_end(ap);
putchar('\n');
}
int main() {
log_message("INFO", "用戶 %s 登錄, ID=%d", "Alice", 1001);
log_message("ERROR", "文件 %s 打開失敗: %s", "data.txt", "權限不足");
return 0;
}
vprintf/vfprintf/vsprintf 族函數接受 va_list,是封裝可變參數日誌/格式化的標準方式。
#include <stdarg.h>
void print_twice(const char *fmt, ...) {
va_list ap1, ap2;
va_start(ap1, fmt);
va_copy(ap2, ap1); // 複製遍歷器
vprintf(fmt, ap1);
printf(" --- 再次: ");
vprintf(fmt, ap2);
printf("\n");
va_end(ap1);
va_end(ap2);
}
| 限制 | 說明 | 對策 |
|---|---|---|
| 無法獲取參數數量/類型 | 調用者需自行約定 (如首參數 count、fmt 字符串) | 設計清晰協議:首參數計數、fmt 解析、哨兵值 |
| 類型不匹配未定義行為 | va_arg(ap, int) 實傳 double 會崩潰/亂碼 | 文檔明確約定,編譯器無法檢查 |
| 無法傳遞位域/寄存器變量 | C 標準禁止 | 先賦值給普通變量再傳遞 |
| 可變參數無法用 register | 標準限制 | 無影響 |
| 性能開銷 | 棧操作、類型轉換 | 熱路徑避免,用固定參數或結構體 |
func(&(struct Options){.a=1, .b=2}) (C99 複合字面量)-Wformat 檢查 printf 族格式化字符串