C:未指定数量的参数 - void foo()

时间:2014-02-27 16:04:53

标签: c function variadic-functions

我在C void foo()中读到here表示a function foo taking an unspecified number of arguments of unspecified type

任何人都可以给我一个例子,其中C函数需要一个未指定数量的参数吗?在C中可以应用哪些内容?我在网上找不到任何东西。

谢谢!

5 个答案:

答案 0 :(得分:17)

这是旧式函数声明。

此声明:

void foo();

声明foo是一个返回void的函数,它接受一个未指定但固定数量和类型的参数。这并不意味着具有任意参数的调用是有效的;这意味着编译器无法使用错误的数字或类型的参数来诊断错误的调用。

某处,也许在另一个翻译单元(源文件)中,必须有一个定义的函数,或许:

void foo(x, y)
long x;
double *y;
{
    /* ... */
}

这意味着对foo的任何调用传递类型longdouble*的两个参数都是无效的,并且具有未定义的行为。< / p>

在1989 ANSI C标准之前,这些是该语言中唯一可用的函数声明和定义,编写正确函数调用的负担完全取决于程序员。 ANSI C添加了 prototypes ,函数声明,指定函数参数的类型,允许编译时检查函数调用。 (这个特性来自早期的C ++。)现代的等价物是:

void foo(long x, double *y);

/* ... */

void foo(long x, double *y) {
    /* ... */
}

旧式(非原型)声明和定义仍然合法,但它们正式过时,这意味着原则上它们可以从该语言的未来版本中删除 - 虽然因为他们仍然在2011年的标准中,我不知道那将会真正发生。

没有充分的理由在现代C代码中使用旧式函数声明和定义。 (我已经看到在一些极端情况下使用它们的论点,但我发现它们并不令人信服。)

C还支持像printf这样的可变参数函数,这些函数确实采用了任意数量的参数,但这是一个独特的特性。必须使用原型声明可变参数函数,原型包括尾随, ...。 (调用没有可见原型的可变参数函数并不违法,但它具有未定义的行为。)函数本身使用<stdarg.h>中定义的宏来处理其参数。与旧式函数声明一样,对, ...对应的参数没有编译时检查(虽然有些编译器可能会检查一些调用;例如gcc警告printf调用中的参数是否为与格式字符串不一致。)

答案 1 :(得分:2)

这实际上意味着你没有告诉编译器函数采用了什么参数,这意味着它不会保护你不用任何任意参数集来调用它。您需要在定义中准确地说明实际上对于要实现的函数采用了哪些参数。

例如,如果你生成一个头文件来描述外部代码中的外来函数,你可以使用它,但是你不知道函数的签名实际上是什么,它仍然可以使用你的标题来调用,但如果你提供调用结果中的错误参数未定义。

答案 2 :(得分:2)

C函数调用标准允许使用零个或多个参数调用函数,并且参数的数量可能与函数接口匹配,也可能不匹配。

这种方式的工作原理是调用者在被调用函数返回后调整堆栈而不是调用堆栈的被调用函数,而不像Pascal这样需要被调用函数来正确管理堆栈调整的其他标准。

因为调用者知道在调用被调用函数之前哪些参数及其类型被压入堆栈而被调用函数没有,所以调用者在被调用函数返回后清除堆栈中的推送参数

使用更新的C标准,函数调用接口描述变得更加复杂,以便允许编译器检测和报告原始K&amp; R C标准允许编译器未检测到的接口问题。

现在的标准是在被调用函数接口规范或声明中使用最后一个已知和指定参数之后的三个句点或点的省略号表示变量参数列表。

因此,对于某些标准C库I / O函数,您会看到以下内容:

 int sprintf (char *buffer, char *format, ...);

这表明函数sprintf要求第一个参数是指向缓冲区的char指针,第二个参数是指向格式字符串的char指针,并且可能还有其他附加参数。在这种情况下,任何其他参数都将是格式字符串中的打印格式说明符需要插入的内容。如果格式字符串只是一个没有格式指定的文本字符串(例如,对于一个整数,例如%d),那么就没有其他参数。

较新的C标准指定了一组用于变量参数列表的函数/宏,即varg函数。使用这些函数/宏,被调用函数可以单步执行参数列表的变量部分并处理参数。这些函数类似于以下内容:

int jFunc (int jj, char *form, ...)
{
   va_list myArgs;
   int     argOne;

   va_start (myArgs, form);
   argOne = va_arg (myArgs, int);
   va_end (myArgs);

   return 0;
}

我们对变量参数列表的问题是C没有办法传递变量参数或甚至多少个参数。因此该功能的设计者必须提供一种机制。在C标准库I / O函数的情况下,这通过指定格式字符串后面的参数数量的格式来完成,方法是为每个参数指定格式说明符。由于没有任何事情可以进行一致性检查,因此您最终可以得到一个格式字符串,该字符串指定多于或少于实际参数,从而导致垃圾输出或输出少于预期。

由于现代C编译器对旧的C源代码具有一定程度的向后兼容性,这意味着您可以使用一些较旧的构造,并且编译器将允许它,但希望有警告。

新功能接口规范旨在减少错误使用功能的可能性。因此新标准建议您使用函数接口声明,以便编译器可以通过检测函数调用中的接口问题和不正确的变量用法来帮助您。

但是,如果你想成为风险承担者,你就不必使用这个安全网,所以如果你愿意,你可以用一个空的参数列表定义一个函数并将它放在一边。

您可能还会在此question about currying in C中找到一个答案,该答案使用变量参数列表以及确定提供了多少参数的方法。

答案 3 :(得分:2)

声明void foo();不一定与采用可变数量参数的函数相同。

所有这一切都是一个函数声明,它指示 nothing 关于函数所使用的参数数量(实际上,这不完全正确,请参阅下面有关参数提升的信息)。声明不是函数原型,它提供有关参数数量和类型的信息(使用省略号表示可变长度参数)。

函数定义(提供函数体)可能需要固定数量的参数,甚至可能有原型。

当使用这样的函数声明时,由程序员调用foo()来获取foo()期望的参数(及其类型)是正确的 - 编译器没有关于参数列表foo()需要。它还限制了函数定义可以使用的参数类型,因为在没有原型的情况下调用函数时,默认参数提升将应用于函数调用的参数。因此,没有原型声明的函数只能期望得到提升的参数。

有关详细信息,请参阅以下C99标准部分:

  

6.7.5.3/14“函数声明符(包括原型)

     

...

     

函数声明符中不属于a的空列表   该函数的定义指定没有关于的信息   提供参数的数量或类型。

     

...

     

6.5.2.2函数调用

     

...

     

如果表示被调用函数的表达式具有类型   不包括原型,执行整数促销   每个参数和类型为float的参数都被提升为   双。这些被称为默认参数促销。如果   参数个数不等于参数个数   行为未定义。如果使用类型定义函数   包括原型,并且原型以省略号结束   (,...)或促销后的参数类型不是   兼容参数的类型,行为是   未定义。如果使用不支持的类型定义函数   包括原型,以及促销后的参数类型   与促销后的参数不兼容,   除以下情况外,行为未定义:

     
      
  • 一个提升类型是有符号整数类型,另一个提升类型是对应的无符号整数类型,值为   两种类型都可以表示;
  •   
  • 这两种类型都是指向字符类型的合格或非限定版本或void的指针。
  •   

答案 4 :(得分:-1)

正如在许多其他答案中所提到的,一个函数采用未指定数量的未指定类型的参数只是意味着它的调用者不知道它的定义。它与必须使用 ... 定义然后在 stdarg.h 中使用 va_list 来获取参数的可变参数函数完全不同

这实际上在过去很常见,并且有许多标准函数使用该功能,例如带有可选最后一个参数的 open()/openat()/_open()

fd = open(filename, O_RDONLY);
fd = open(filename, O_CREAT | O_WRONLY,  0777);

open() function parameters

main() 也是一个可以接收不同数量参数的函数

int main();
int main(int argc, char **argv);
int main(int argc, char **argv, char **envp);
int main(int argc, char **argv, char **envp, char **apple);

事实上,Windows x64 调用约定是 designed to support such unspecified number of arguments 所以对于大多数现代用例来说有点不幸