12.2 可变参数
可变参数函数允许形参个数在调用时变化,典型例子是 printf。这类函数的核心在 <stdarg.h>:va_list、va_start、va_arg、va_end(以及 va_copy)。
1. 约束与设计要点
可变参数部分不会携带自动类型信息,函数本身无法“猜出”每个实参的真实类型和数量。因此你必须显式提供协议,例如“第一个形参给出数量”或“使用格式字符串描述类型”。没有协议的可变参数接口几乎必然不可维护。
2. 一个安全的最小模式
c
#include <stdarg.h>
#include <stddef.h>
long long sum_ints(size_t count, ...) {
va_list ap;
long long total = 0;
va_start(ap, count);
for (size_t i = 0; i < count; ++i) {
total += va_arg(ap, int);
}
va_end(ap);
return total;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
该函数把 count 作为协议字段,调用方与被调方都能确定读取边界。
3. 默认实参提升
在可变参数部分,float 会提升为 double,较窄整数类型会提升为 int 或 unsigned int。因此 va_arg 取值类型必须与提升后类型一致,否则行为未定义。
4. va_copy 的使用场景
当同一组可变实参需要被遍历不止一次时,应使用 va_copy 复制实参游标。直接重复读取同一个 va_list 会导致未定义行为。
c
#include <stdarg.h>
int consume_twice(int n, ...) {
va_list ap;
va_list ap2;
va_start(ap, n);
va_copy(ap2, ap);
/* 第一次读取 ap ... */
/* 第二次读取 ap2 ... */
va_end(ap2);
va_end(ap);
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
5. 前向转发模式
可变参数封装函数常见写法是“外层收集,内层 v* 接口处理”,例如 printf 对应 vprintf。这种分层能减少重复实现,也让协议检查集中在一个入口上。