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

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

任何人都可以给我或指向一个C函数采用未指定数量的参数的示例吗? 在C可以应用什么? 我在网上找不到任何东西。

谢谢!

这是一个旧式的函数声明。

本声明:

 void foo(); 

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

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

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

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

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

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

旧式(非原型)声明和定义仍然是合法的,但它们已经正式淘汰 ,这意味着原则上它们可以从该语言的未来版本中删除 – 尽管它们仍然存在于2011标准我不知道那将会发生。

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

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

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

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

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

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

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

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

函数声明符中的空列表不是该函数定义的一部分,它指定不提供有关参数数量或类型的信息。

6.5.2.2函数调用

如果表示被调用函数的表达式具有不包含原型的类型,则对每个参数执行整数提升,并将具有float类型的参数提升为double。 这些被称为默认参数促销。 如果参数的数量不等于参数的数量,则行为是未定义的。 如果使用包含原型的类型定义函数,并且原型以省略号(,…)结尾,或者促销后的参数类型与参数类型不兼容,则行为未定义。 如果使用不包含原型的类型定义函数,并且促销后的参数类型与促销后的参数类型不兼容,则行为未定义,但以下情况除外:

  • 一个提升类型是有符号整数类型,另一个提升类型是相应的无符号整数类型,并且该值可在两种类型中表示;
  • 这两种类型都是指向字符类型或void的限定或非限定版本的指针。

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

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

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

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

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

使用更新的C标准,函数调用接口描述变得更加复杂,以便允许编译器检测和报告原始K&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没有办法传递变量参数甚至多少个参数。 因此该function的设计者必须提供一种机制。 在C标准库I / O函数的情况下,这通过指定格式字符串后面的参数数量的格式来完成,方法是为每个参数指定格式说明符。 由于没有任何事情可以进行一致性检查,因此您最终可以得到一个格式字符串,该字符串指定多于或少于实际参数,从而导致垃圾输出或输出少于预期。

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

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

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

你也可以在C中讨论使用变量参数列表以及确定提供了多少参数的方法。