main ( ){
main(10,"abc",12.28);
//Works fine !
//It won't give the error. The code will compile successfully.
//(May cause Segmentation fault when run)
}
int f(int x, int y);
^^^^^^^^^^^^^^^
int f(int x, int y) { return x + y; }
^^^^^^^^^^^^^^^
int f();
^^^
int f(x, y) int x; int y; { return x + y; }
^^^^^^^
6 回答
这是在C中说“无参数”的正确方法,它也适用于C语言 .
但:
在C和C中意味着不同的东西!在C中它表示"could take any number of parameters of unknown types",在C中它表示与
foo(void)
相同 .变量参数列表函数本质上是非类型安全的,应尽可能避免 .
在C中指定参数有两种方法 . 一种是使用标识符列表,另一种是使用参数类型列表 . 标识符列表可以省略,但类型列表不能 . 因此,假设一个函数在函数定义中不带参数,则使用(省略的)标识符列表执行此操作
这有一个参数类型列表:
如果在参数类型列表中,只有一个参数类型为void(它必须没有名称),那么这意味着该函数不带参数 . 但是这两种定义函数的方式在它们声明的内容上有所不同 .
标识符列表
第一个定义该函数采用特定数量的参数,但既不传递计数也不传递所需的类型 - 就像使用标识符列表的所有函数声明一样 . 因此呼叫者必须事先知道类型和计数 . 因此,如果调用者调用函数给它一些参数,则行为是未定义的 . 例如,堆栈可能会损坏,因为被调用的函数在获得控制权时需要不同的布局 .
不推荐在函数参数中使用标识符列表 . 它在过去使用过,并且仍然存在于许多 生产环境 代码中 . 由于这些参数提升,它们可能会导致严重的危险(如果提升的参数类型与函数定义的参数类型不匹配,行为也未定义!)并且当然不那么安全 . 所以总是在没有参数的函数中使用
void
thingy,仅用于声明和函数定义 .参数类型列表
第二个定义函数接受零参数并且还传达 - 就像使用参数类型列表声明函数的所有情况一样,它被称为
prototype
. 如果调用者调用该函数并给它一些参数,那就是一个错误,编译器会发出一个适当的错误 .声明函数的第二种方法有很多好处 . 当然之一是检查参数的数量和类型 . 另一个区别是,因为编译器知道参数类型,所以它可以将参数的隐式转换应用于参数类型 . 如果不存在参数类型列表,则无法完成,并且参数将转换为提升类型(称为默认参数提升) . 例如,
char
将变为int
,而float
将变为double
.函数的复合类型
顺便说一下,如果文件包含省略的标识符列表和参数类型列表,则参数类型列表“wins” . 最后的函数类型包含一个原型:
那是因为两个声明都没有说出任何矛盾的说法 . 然而,第二个问题还有待说明 . 这是一个被接受的论点 . 反过来也可以这样做
第一个使用标识符列表定义函数,而第二个使用包含参数类型列表的声明为其提供原型 .
void foo(void)
更好,因为它明确地说:不允许参数 .void foo()
意味着你可以(在一些编译器下)发送参数,至少如果这是你的函数的声明而不是它的定义 .在C中,
main()
和main(void)
之间存在 no 差异 .But 在C中,将使用任意数量的参数调用
main()
.例:
main(void)
将在没有任何参数的情况下调用 . 如果我们尝试通过,那么最终导致编译器错误 .例:
C99 quotes
这个答案旨在引用和解释C99 N1256 standard draft的相关部分 .
Definition of declarator
声明者这个词会出现很多,所以让我们理解它 .
从语言语法中,我们发现以下下划线字符是声明符:
声明符是函数声明和定义的一部分 .
有两种类型的声明符:
参数类型列表
标识符列表
Parameter type list
声明如下:
定义如下:
它被称为参数类型列表,因为我们必须给出每个参数的类型 .
Identifier list
定义如下:
声明如下:
我们不能声明具有非空标识符列表的函数:
因为6.7.5.3 "Function declarators (including prototypes)"说:
它被称为标识符列表,因为我们只在
f(x, y)
上给出标识符x
和y
,类型后来 .这是一种较旧的方法,不应再使用 . 6.11.6函数声明符说:
引言解释了什么是过时功能:
f() vs f(void) for declarations
当你写的时候:
它必然是一个标识符列表声明,因为6.7.5 "Declarators"表示将语法定义为:
所以只有标识符列表版本可以为空,因为它是可选的(
_opt
) .direct-declarator
是唯一定义声明符的括号(...)
部分的语法节点 .那么我们如何消除歧义并使用没有参数的更好的参数类型列表呢? 6.7.5.3函数声明符(包括原型)说:
所以:
就是这样 .
这是一个明确允许的神奇语法,因为我们不能以任何其他方式使用
void
类型参数:What can happen if I use an f() declaration?
也许代码编译得很好:6.7.5.3函数声明符(包括原型):
所以你可以逃脱:
其他时候,UB可以爬起来(如果你很幸运,编译器会告诉你),你将很难搞清楚原因:
见:Why does an empty declaration work for definitions with int arguments but not for float arguments?
f() and f(void) for definitions
VS
是相似的,但不完全相同 .
6.7.5.3函数声明符(包括原型)说:
看起来类似于
f(void)
的描述 .但仍然......似乎:
符合未定义的行为,同时:
不符合要求,如:Why does gcc allow arguments to be passed to a function defined to be with no arguments?
TODO明白为什么 . 与原型有关还是与原型有关 . 定义原型 .
除了语法上的差异,许多人也更喜欢使用
void function(void)
出于实际原因:如果您正在使用搜索功能并想要找到该功能的实现,您可以搜索
function(void)
,它将返回原型以及实现 .如果省略了第二个
void
,则必须搜索function()
,因此也会查找所有函数调用,这使得查找实际实现变得困难 .