可变参数函数
C语言函数参数从右向左压栈的设计方式,延伸出了函数可以有不确定个数参数的特性,称为可变参数函数。我们经常使用的printf()就是一个可变参数函数,它有一个强制参数,即格式化字符串。格式化字符串中的转换修饰符决定了可选参数的数量和类型。
printf("glimix.com");
printf("glimix.com %s-%d", "zyf", 2026);
一个可变参数的函数,需要固定数量的强制参数,后面是数量可变的可选参数,可变参数用省略号(...)指定参数表,且只能有一个(...)参数,而且必须位于参数末尾。
void foo(...); // 错误:没有强制参数
void foo(param_list, ... ); // 正确:有强制参数列表param_list
还记得之前我们曾使用一个char*指针访问函数所有参数的手法么?让我们故技重施,来把它应用到可变参数函数上,看看效果如何。
#include <stdio.h>
void param_list(int count, ...)
{
int *p = &count;
p += 1; // 指向第一个可变元素
printf("params: ");
for (int i = 0; i < count; i++)
{
printf("%d, ", *p++);
}
printf("\n");
}
int main()
{
param_list(5, -1, 9, 6, 1, 2 );
param_list(7, 0, 2, -6, 1, 2, 5, 8 );
}

param_list()第一个参数count指定了后面元素的数量,并假定元素的类型都是int。然后使用int*与指针算术运算对数值逐一输出。对于指针算术运算,现在只需要知道,p += 1;p++;都是将指针p向前移动一个型型大小,即移动到下一个元素处。
从程序运行的结果图来看,好像一切都还不错。但是C语言对于可变参数的访问提供了更加安全的访问方式,这就是一系列是的va_xxx宏。你可能在想,既然我们能用char*指针加上一定的类型偏移量就能得到所有的参数,还用va_xxx宏做什么?事实上,上述做法都是在一种理想状态下,假如编译器实现者在可变参数的实现中,加上了一些辅助数据用于记录某些信息,那通过指针算法就不能得到正确的参数地址了。因此,为了保证统一性,请使用va_xxx宏。
宏 va_xxx
为了解析这些不确定的参数,你需要包括<stdarg.h>文件,然后使用其中定义的几个宏来完成需求。
typedef char* va_list;
void va_start(va_list ap, prev_param); /* ANSI version */
type va_arg(va_list ap, type);
void va_end(va_list ap);
va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。这与之前我们使用char*指针,通过指针算术运算,将函数参数逐一输出类似。在这些宏的实现中,va_list的作用与此类似。
下面是使用这些宏的步骤:
- 定义va_list类型的变量ap。
- 使用va_start宏初始化ap,第一个参数是ap本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数,初始化的目的是使ap指向可变参数表的第一个参数,
- 使用va_arg返回可变的参数,它的第一个参数是ap,第二个参数是要获取的参数的指定类型。当数据返回后,va_arg内部会把ap的位置指向变参表的下一个变量位置。
- 最后用va_end宏结束可变参数的获取。如果函数有多个可变参数的,依次调用va_arg获取各个参数。
#include <stdio.h>
#include <stdarg.h>
#include <limits.h>
int max_list(char *msg, int count, ...)
{
int temp;
int max_value = INT_MIN;
va_list ap;
va_start(ap, count);
for (int i = 0; i < count; i++)
{
temp = va_arg(ap, int);
if (temp > max_value)
max_value = temp;
}
va_end(ap);
printf("%s ", msg);
return max_value;
}
int main()
{
printf("max value: %d\n", max_list("list 1", 6, 2, 5, 9, 6, 1, 2));
printf("max value: %d\n", max_list("list 2", 7, 12, 25, -5, 59, 65, 11, 26));
}

注意:va_start 宏第二个参数要求是(...)之前的那个参数,即最后一个强制参数,在max_list中就是count。想想看,既然要获取的是可变参数列表中的值,那使用它的前一个位置是理所当然的。关于这点,语言标准中也是如此说明。
va_start
void va_start (va_list ap, paramN);
paramN
- Name of the last named parameter in the function definition.
- The arguments extracted by subsequent calls to va_arg are those after paramN.
不可传递
最后记得,可变参数列表不可通过函数再次传递。
void error_exit(const char *fmt, ...)
{
}
void error_dump(const char *fmt, ...)
{
// 错误,可变参数列表不可再次传递
error_exit(fmt, ...);
}
int main()
{
error_dump("", 1, 3.14, "glimix.com");
}