我读到here,在C中void foo()的意思是a function foo taking an unspecified number of arguments of unspecified type。 有没有人给予我一个例子,其中一个C函数接受了一个未指定数量的参数?这在C中可以应用到什么?我在网上找不到任何东西。
型 变量参数列表的问题是C没有一种方法来传递变量参数,甚至没有多少参数。因此函数的设计者必须提供一种机制。在C标准库I/O函数,这是通过为每个参数指定格式说明符来指示格式字符串后面的参数数量的格式来完成的。没有做一致性检查,你可以结束一个格式字符串,指定更多或更少的实际参数,导致垃圾输出或比预期少的输出。 由于现代C编译器对旧的C源代码有一定程度的向后兼容性,这意味着你可以使用一些旧的结构,编译器将允许它,尽管希望有一个警告。 新的函数接口规范旨在减少错误使用函数的机会。因此,新标准建议您使用函数接口声明,以便编译器可以通过检测接口问题和函数调用中错误的变量使用来帮助您。 然而,如果你想成为一个冒险者,你不必使用这个安全网,所以如果你愿意,你可以定义一个空参数列表的函数,然后运行它。 您还可能会发现我在这个question about currying in C中放置的一个答案,它使用变量参数列表沿着确定提供了多少个参数。
6条答案
按热度按时间4szc88ey1#
这是一个 * 旧式 * 函数声明。
本声明:
字符串
声明
foo
是一个返回void
的函数,它接受一个未指定但固定数量和类型的参数。这并不意味着带有任意参数的调用是有效的;这意味着编译器无法诊断带有错误数量或类型的参数的错误调用。在某个地方,也许在另一个翻译单元(源文件)中,必须有一个函数的 * 定义 *,也许:
型
这意味着任何对
foo
的调用如果 * 不 * 传递两个类型为long
和double*
的参数都是无效的,并且具有未定义的行为。在1989年ANSI C标准之前,这些是语言中唯一可用的函数声明和定义,编写正确函数调用的负担完全在程序员身上。ANSI C添加了 * 原型 *,指定函数参数类型的函数声明,它允许对函数调用进行编译时检查。(这个特性是从早期的C++借用的。)上面的现代等价物是:
型
旧式的(非原型的)声明和定义仍然是法律的,但它们正式 * 过时 * 了,这意味着,原则上,它们可能会从语言的未来版本中删除--尽管由于它们仍然存在于2011年的标准中,我不知道这是否真的会发生。
在现代C代码中使用旧式的函数声明和定义没有什么好的理由(我看到过在某些极端情况下使用它们的论点,但我觉得它们没有说服力)。
C也支持 variadic 函数,比如
printf
,它可以接受任意数量的参数,但这是一个独特的特性。variadic函数必须声明一个原型,其中包括一个尾随的, ...
。(调用没有可见原型的可变参数函数并不违法,但它有未定义的行为。)函数本身使用在<stdarg.h>
中定义的宏来处理其参数。与旧式函数声明一样,不存在对与, ...
对应的参数的编译时检查(尽管某些编译器可能检查某些调用;例如,如果printf
调用中的参数与格式字符串不一致,gcc会发出警告)。kqlmhetl2#
这字面上的意思是你没有告诉编译器函数接受什么参数,这意味着它不会保护你用任何任意的参数集调用它。你需要在定义中精确地说明函数实际上接受什么参数来实现。
例如,如果你正在生成一个头文件来描述外部代码中的外部函数,但你不知道函数的签名实际上是什么,那么它仍然可以使用你的头文件来调用,但如果你在调用中提供了错误的参数,则结果是未定义的。
km0tfn4u3#
声明
void foo();
不一定与接受可变数量参数的函数相同。它只是一个函数声明,它没有指明函数接受的参数数量(实际上,这并不完全正确,请参阅下面的参数提升)。声明不是函数原型,它提供了有关参数数量和类型的信息(使用省略号表示可变长度参数)。
函数 definition(提供函数体的地方)可能接受固定数量的参数,甚至可能有一个原型。
当使用这样的函数声明时,由程序员调用
foo()
来获取foo()
所需的参数(及其类型)正确-编译器没有关于foo()
需要的参数列表的信息。它还限制了函数定义可以使用的参数类型,因为当函数在没有原型的情况下被调用时,因此,默认的参数提升将应用于函数调用时的参数。因此,没有原型声明的函数只能获得提升的参数。有关详细信息,请参阅以下C99标准部分:
6.7.5.3/14“函数声明符(包括原型)
...
如果函数声明符中的空列表不是该函数定义的一部分,则指定不提供有关参数数量或类型的信息。
...
6.5.2.2函数调用
...
如果表示被调用函数的表达式的类型不包括原型,则对每个参数执行整数提升,并且将类型为float的参数提升为double。这些称为默认参数提升。如果参数的数量不等于参数的数量,则行为未定义。如果函数是用包括原型的类型定义的,要么原型以省略号结尾(,...)或提升后的实参类型与形参类型不兼容,则行为未定义。如果函数定义的类型不包含原型,且提升后的实参类型与提升后的形参类型不兼容,则行为未定义,以下情况除外:
j1dl9f464#
C函数调用标准允许使用零个或多个参数调用函数,参数的数量可能与函数接口匹配,也可能不匹配。
它的工作方式是在被调用函数返回后由调用者调整堆栈,而不是由被调用函数调整堆栈,这与其他标准(如Pascal)不同,Pascal要求被调用函数正确管理堆栈调整。
因为调用者知道在被调用函数被调用之前哪些参数及其类型已经被推入堆栈,而被调用函数不知道,所以在被调用函数返回后,由调用者从堆栈中清除推入的参数。
随着更新的C标准,函数调用接口描述变得更加复杂,以便允许编译器检测和报告原始K&R C标准允许编译器未检测到的接口问题。
现在的标准是,在被调用函数的接口规范或声明中,在最后一个已知和指定的参数之后使用三个句点或点的省略号表示法来指定变量参数列表。
因此,您将看到一些标准C库I/O函数的如下内容:
字符串
这表明函数sprintf要求第一个参数是指向缓冲区的字符指针,第二个参数是指向格式字符串的字符指针,并且可能存在其他附加参数。在这种情况下,任何附加参数将是需要为格式字符串中的打印格式说明符插入的内容。如果格式字符串只是没有格式指定的文本字符串,(例如,对于整数,类似于%d),则不会有其他参数。
较新的C标准指定了一组函数/宏用于变量参数列表,即varg函数。通过这些函数/宏,被调用的函数可以遍历参数列表的变量部分并处理参数。这些函数看起来像下面这样:
型
变量参数列表的问题是C没有一种方法来传递变量参数,甚至没有多少参数。因此函数的设计者必须提供一种机制。在C标准库I/O函数,这是通过为每个参数指定格式说明符来指示格式字符串后面的参数数量的格式来完成的。没有做一致性检查,你可以结束一个格式字符串,指定更多或更少的实际参数,导致垃圾输出或比预期少的输出。
由于现代C编译器对旧的C源代码有一定程度的向后兼容性,这意味着你可以使用一些旧的结构,编译器将允许它,尽管希望有一个警告。
新的函数接口规范旨在减少错误使用函数的机会。因此,新标准建议您使用函数接口声明,以便编译器可以通过检测接口问题和函数调用中错误的变量使用来帮助您。
然而,如果你想成为一个冒险者,你不必使用这个安全网,所以如果你愿意,你可以定义一个空参数列表的函数,然后运行它。
您还可能会发现我在这个question about currying in C中放置的一个答案,它使用变量参数列表沿着确定提供了多少个参数。
py49o6xq5#
从C23开始,旧式(K&R)函数声明和定义已经从C语言中删除。
该变更由提案N2841: No function declarators without prototypes引入,其总和为:
这将移除对没有原型的函数声明符的过时支持。没有原型的函数声明符的旧语法被赋予C++语义。
引用现行C23标准草案(N3096,附录M.2第五版):
第五版(
__STDC_VERSION__
202311L
)的主要变化包括...
void;
的参数列表形式为
void foo()
的函数声明现在完全等同于void foo(void)
,就像在C++中一样。6ss1mwsb6#
正如在许多其他答案中提到的,一个函数接受未指定数量的未指定类型的参数只是意味着它的调用者不知道它的定义。这与可变参数函数完全不同,在可变参数函数中,您必须使用
...
定义,然后在stdark. h中使用va_list
来获取参数实际上,它在过去很常见,有许多标准函数使用该功能,例如带有可选last参数的
open()/openat()
/_open()
字符串
参见open() function parameters
main()
也是一个可以接收不同数量参数的函数型
实际上,Windows x64的调用约定是designed to support such unspecified number of arguments,所以对于大多数现代用例来说,这有点不幸