什么是更好的:void foo()
或void foo(void)
?随着虚空,它看起来丑陋和不一致,但我被告知它是好的.这是真的?
编辑:我知道一些旧的编译器做了奇怪的事情,但如果我只使用GCC,那void foo()
好吗?将foo(bar);
随后被接受?
void foo(void);
这是在C中说"无参数"的正确方法,它也适用于C++.
但:
void foo();
在C和C++中意味着不同的东西!在C中它意味着"可以采用任意数量的未知类型的参数",而在C++中它意味着相同foo(void)
.
变量参数列表函数本质上是非类型安全的,应尽可能避免.
在C中指定参数有两种方法.一种是使用标识符列表,另一种是使用参数类型列表.标识符列表可以省略,但类型列表不能.因此,假设一个函数在函数定义中不带参数,则使用(省略的)标识符列表执行此操作
void f() { /* do something ... */ }
这有一个参数类型列表:
void f(void) { /* do something ... */ }
如果在参数类型列表中,只有一个参数类型为void(它必须没有名称),那么这意味着该函数不带参数.但是这两种定义函数的方式在它们声明的内容上有所不同.
第一个定义函数采用特定数量的参数,但既不传递计数也不传递所需的类型 - 就像使用标识符列表的所有函数声明一样.因此呼叫者必须事先知道类型和计数.因此,如果调用者调用函数给它一些参数,则行为是未定义的.例如,堆栈可能会损坏,因为被调用的函数在获得控制时需要不同的布局.
不推荐在函数参数中使用标识符列表.它在过去使用过,并且仍然存在于许多生产代码中.由于这些参数提升,它们可能会导致严重的危险(如果提升的参数类型与函数定义的参数类型不匹配,行为也未定义!)并且当然不那么安全.所以总是在void
没有参数的函数中使用thingy,仅用于声明和函数定义.
第二个定义该函数接受零参数并且还传达它 - 就像使用参数类型列表声明函数的所有情况一样,它被称为a prototype
.如果调用者调用该函数并给它一些参数,那就是一个错误,并且编译器会发出一个适当的错误.
声明函数的第二种方法有很多好处.当然之一是检查参数的数量和类型.另一个区别是,因为编译器知道参数类型,所以它可以将参数的隐式转换应用于参数类型.如果不存在参数类型列表,则无法执行,并且参数将转换为提升类型(称为默认参数提升).char
将成为int
,例如,当float
将成为double
.
顺便说一下,如果文件包含省略的标识符列表和参数类型列表,则参数类型列表"wins".最后的函数类型包含一个原型:
void f(); void f(int a) { printf("%d", a); } // f has now a prototype.
那是因为两个声明都没有说出任何矛盾的说法.然而,第二个还有话要说.这是一个被接受的论点.反过来也可以这样做
void f(a) int a; { printf("%d", a); } void f(int);
第一个使用标识符列表定义函数,而第二个使用包含参数类型列表的声明为其提供原型.
void foo(void)
更好,因为它明确地说:不允许参数.
void foo()
意味着你可以(在一些编译器下)发送参数,至少如果这是你的函数的声明而不是它的定义.
C99报价
这个答案旨在引用和解释C99 N1256标准草案的相关部分.
声明者的定义
声明者这个词会出现很多,所以让我们理解它.
从语言语法中,我们发现以下下划线字符是声明符:
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; } ^^^^^^^
声明符是函数声明和定义的一部分.
有两种类型的声明符:
参数类型列表
标识符列表
参数类型列表
声明如下:
int f(int x, int y);
定义如下:
int f(int x, int y) { return x + y; }
它被称为参数类型列表,因为我们必须给出每个参数的类型.
标识符列表
定义如下:
int f(x, y) int x; int y; { return x + y; }
声明如下:
int g();
我们不能声明具有非空标识符列表的函数:
int g(x, y);
因为6.7.5.3"函数声明符(包括原型)"说:
3函数声明符中不属于该函数定义的标识符列表应为空.
它被称为标识符列表,因为我们只提供标识符x
和y
on f(x, y)
,类型后来.
这是一种较旧的方法,不应再使用了.6.11.6函数声明符说:
1使用带有空括号的函数声明符(不是prototype-format参数类型声明符)是一个过时的功能.
和简介解释了什么是过时的功能:
某些功能已过时,这意味着它们可能会在本国际标准的未来版本中考虑撤销.由于它们的广泛使用,它们被保留,但是它们在新实现(用于实现功能)或新程序(用于语言[6.11]或库功能[7.26])中的使用不鼓励使用
f()vs f(void)用于声明
当你写的时候:
void f();
它必然是一个标识符列表声明,因为6.7.5"声明者"说将语法定义为:
direct-declarator: [...] direct-declarator ( parameter-type-list ) direct-declarator ( identifier-list_opt )
所以只有标识符列表版本可以为空,因为它是可选的(_opt
).
direct-declarator
是唯一定义(...)
声明符的括号部分的语法节点.
那么我们如何消除歧义并使用没有参数的更好的参数类型列表呢?6.7.5.3函数声明符(包括原型)说:
10 void类型的未命名参数作为列表中唯一项的特殊情况指定该函数没有参数.
所以:
void f(void);
就是这样.
这是一个明确允许的神奇语法,因为我们不能void
以任何其他方式使用类型参数:
void f(void v); void f(int i, void); void f(void, int);
如果我使用f()声明会发生什么?
也许代码编译得很好:6.7.5.3函数声明符(包括原型):
14函数声明符中不属于该函数定义的空列表指定不提供有关参数数量或类型的信息.
所以你可以逃脱:
void f(); void f(int x) {}
其他时候,UB可以爬起来(如果你很幸运,编译器会告诉你),你将很难搞清楚原因:
void f(); void f(float x) {}
请参阅:为什么空声明适用于带有int参数的定义但不适用于浮点参数?
定义的f()和f(void)
f() {}
VS
f(void) {}
是相似的,但不完全相同.
6.7.5.3函数声明符(包括原型)说:
14函数声明符中的空列表是该函数定义的一部分,指定该函数没有参数.
看起来类似于描述f(void)
.
但仍然......似乎:
int f() { return 0; } int main(void) { f(1); }
符合未定义的行为,同时:
int f(void) { return 0; } int main(void) { f(1); }
是不符合的,如下所述:为什么gcc允许将参数传递给定义为不带参数的函数?
TODO明白为什么.与原型有关还是与原型有关.定义原型.