跳转到内容

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;
{
    /* ... */
}

stdarg.h 类型

[编辑 | 编辑源代码]
名称 描述 兼容性
va_list 用于迭代参数的类型 C89

stdarg.h

[编辑 | 编辑源代码]
名称 描述 兼容性
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 中。

没有定义用于确定传递给函数的未命名参数的数量或类型的机制。函数只需知道或确定这一点,方法各不相同。常见约定包括

  • 使用包含指示参数类型的嵌入式说明符的 printfscanf 风格的格式字符串。
  • 可变参数结尾处的哨兵值。
  • 指示可变参数数量的计数参数。

类型安全

[编辑 | 编辑源代码]

一些 C 实现提供了 C 扩展,允许编译器检查格式字符串和哨兵的正确使用。除了这些扩展,编译器通常无法检查传递的未命名参数是否为函数期望的类型,或者将它们转换为所需的类型。因此,应注意确保这方面的正确性,因为如果类型不匹配,会导致未定义的行为。

例如,如果调用 va_arg(ap, short *),则必须在调用中将该参数的空指针传递为 (short *)0;使用 NULL 是不正确的,因为指向不同数据类型的指针可能具有不同的尺寸。(short *)NULL 可以用作替代,但没有任何作用,并且可能会误导不经意的读者,使其认为不需要强制转换。

另一个需要考虑的是应用于未命名参数的默认参数提升。float 会自动提升为 double。同样,比 int 窄的类型的参数将提升为 intunsigned int。接收未命名参数的函数必须期望提升后的类型。

GCC 具有一个扩展,用于检查传递的参数

format(archetype, string-index, first-to-check)
格式属性指定函数采用 printfscanfstrftimestrfmon 风格的参数,这些参数应针对格式字符串进行类型检查。例如,声明
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);
}

varargs.h

[编辑 | 编辑源代码]

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]

参考文献

[编辑 | 编辑源代码]
  1. "IEEE Std 1003.1 stdarg.h". 检索于 2009-07-04.
  2. "Single UNIX Specification varargs.h". 检索于 2007-08-01.
华夏公益教科书