C 编程/stdarg.h
stdarg.h
是一个头文件,允许函数接受不定数量的参数。[1] 它提供了遍历未知数量和类型函数参数列表的功能。
stdarg.h
的内容通常用于可变参数函数,但也可以在可变参数函数调用的其他函数(例如 vprintf
)中使用。
可变参数函数是可以接受可变数量参数的函数,它们在最后一个参数的位置使用省略号 (...
) 声明。printf
就是这样一个函数的例子。一个典型的声明是
int check(int a, double b, ...);
可变参数函数必须至少有一个命名参数,因此例如
char *wrong(...);
在 C 中是不允许的。(在 C++ 中,这种声明是允许的,但并不实用。)在 C 中,逗号必须放在省略号之前;在 C++ 中,逗号是可选的。
定义中使用相同的语法
long func(char, double, int, ...);
long func(char a, double b, int c, ...)
{
/* ... */
}
省略号也可以出现在旧式函数定义中
long func();
long func(a, b, c, ...)
char a;
double b;
{
/* ... */
}
名称 | 描述 | 兼容性 |
---|---|---|
va_list |
用于迭代参数的类型 | C89 |
名称 | 描述 | 兼容性 |
---|---|---|
va_start |
使用 va_list 开始迭代参数 |
C89 |
va_arg |
检索参数 | C89 |
va_end |
释放 va_list |
C89 |
va_copy |
将一个 va_list 的内容复制到另一个 va_list |
C99 |
要访问未命名的参数,必须在可变参数函数中声明一个类型为 va_list
的变量。然后使用 va_start
宏调用它,它有两个参数:第一个是声明为 va_list
类型的变量,第二个是函数最后一个命名参数的名称。在此之后,每次调用 va_arg
宏都会产生下一个参数。va_arg
的第一个参数是 va_list
,第二个参数是传递给函数的下一个参数的类型。最后,在函数返回之前必须对 va_list
调用 va_end
宏。(不需要读取所有参数。)
C99 提供了一个额外的宏 va_copy
,它可以复制 va_list
的状态。宏调用 va_copy(va2, va1)
将 va1
复制到 va2
中。
没有定义用于确定传递给函数的未命名参数的数量或类型的机制。函数只需要以某种方式知道或确定这一点,具体方法各不相同。常见的约定包括
- 使用
printf
或scanf
样式的格式字符串,其中包含指示参数类型的嵌入式说明符。 - 可变参数结尾处的哨兵值。
- 一个表示可变参数数量的计数参数。
一些 C 实现提供 C 扩展,允许编译器检查格式字符串和哨兵的正确使用。在没有这些扩展的情况下,编译器通常无法检查传递的未命名参数是否与函数期望的类型匹配,或者将它们转换为所需的类型。因此,应注意确保这方面的正确性,因为如果类型不匹配,将导致未定义的行为。
例如,如果调用 va_arg(ap, short *)
,则必须在调用中将该参数的空指针传递为 (short *)0
;使用 NULL
是错误的,因为指向不同数据类型的指针的大小可能不同。(short *)NULL
可以使用,但没有好处,而且可能会让不注意的读者认为强制转换不是必需的。
另一个需要考虑的是应用于未命名参数的默认参数提升。float
会自动提升为 double
。同样,类型比 int
窄的参数将提升为 int
或 unsigned int
。接收未命名参数的函数必须期望提升后的类型。
GCC 有一个扩展可以检查传递的参数
format(archetype, string-index, first-to-check)
- format 属性指定函数接受
printf
、scanf
、strftime
或strfmon
样式的参数,这些参数应根据格式字符串进行类型检查。例如,声明会导致编译器检查对extern int my_printf (void *my_object, const char *my_format, ...) __attribute__ ((format (printf, 2, 3)));my_printf
的调用中的参数,以确保它们与printf
样式的格式字符串参数my_format
一致。—"5.27 Extensions to the C Language Family - Declaring Attributes of Functions". Retrieved 2009-01-03.
#include <stdio.h>
#include <stdarg.h>
/* print all non-negative args one at a time;
all args are assumed to be of int type */
void printargs(int arg1, ...)
{
va_list ap;
int i;
va_start(ap, arg1);
for (i = arg1; i >= 0; i = va_arg(ap, int))
printf("%d ", i);
va_end(ap);
putchar('\n');
}
int main(void)
{
printargs(5, 2, 14, 84, 97, 15, 24, 48, -1);
printargs(84, 51, -1);
printargs(-1);
printargs(1, -1);
return 0;
}
该程序将产生以下输出
5 2 14 84 97 15 24 48 84 51 1
要从函数内部调用其他可变参数函数(如 sprintf),您需要使用该函数的可变参数版本(本例中为 vsprintf)
void MyPrintf(const char* format, ...)
{
va_list args;
char buffer[BUFSIZ];
va_start(args,format);
vsprintf (buffer, format, args );
FlushFunnyStream(buffer);
va_end(args);
}
POSIX 定义了遗留头文件 varargs.h
,它可以追溯到 C 标准化之前,并提供与 stdarg.h
相似的功能。这个头文件不是 ISO C 的一部分。该文件,如 Single UNIX Specification 的第二个版本中定义的那样,只包含 C89 stdarg.h
的所有功能,但以下情况除外:它不能用于标准 C 新式定义;您可以选择不传递给定参数(标准 C 要求至少传递一个参数);它的工作方式也不同——在标准 C 中,您将编写
#include <stdarg.h>
int summate(int n, ...)
{
va_list ap;
int i = 0;
va_start(ap, n);
for (; n; n--)
i += va_arg(ap, int);
va_end(ap);
return i;
}
或者使用旧式函数定义
#include <stdarg.h>
int summate(n, ...)
int n;
{
/* ... */
}
并使用以下方式调用
summate(0);
summate(1, 2);
summate(4, 9, 2, 3, 2);
使用 varargs.h
,函数将为
#include <varargs.h>
summate(n, va_alist)
va_dcl /* no semicolon here! */
{
va_list ap;
int i = 0;
va_start(ap);
for (; n; n--)
i += va_arg(ap, int);
va_end(ap);
return i;
}
并且调用方式相同。
varargs.h
需要旧式函数定义,因为实现方式不同。[2]
- ↑ "IEEE Std 1003.1
stdarg.h
". Retrieved 2009-07-04. - ↑ "Single UNIX Specification
varargs.h
". Retrieved 2007-08-01.