跳转到内容

C 编程/stdarg.h

来自 Wikibooks,开放的书籍,开放的世界

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)
format 属性指定函数接受 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". Retrieved 2009-07-04.
  2. "Single UNIX Specification varargs.h". Retrieved 2007-08-01.
华夏公益教科书