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)
- 格式属性指定函数采用
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
". 检索于 2009-07-04. - ↑ "Single UNIX Specification
varargs.h
". 检索于 2007-08-01.