更好的是:void foo()
还是void foo(void)
?
随着虚空,它看起来丑陋和不一致,但我被告知它是好的。这是真的吗?
编辑:我知道一些旧的编译器做了奇怪的事情,但如果我只使用GCC,那么void foo()
好吗?那么foo(bar);
会被接受吗?
答案 0 :(得分:229)
void foo(void);
这是在C中说“无参数”的正确方法,它也适用于C ++。
可是:
void foo();
在C和C ++中意味着不同的东西!在C中它意味着“可以使用任意数量的未知类型的参数”,而在C ++中它意味着与foo(void)
相同。
变量参数列表函数本质上是非类型安全的,应尽可能避免使用。
答案 1 :(得分:93)
在C中指定参数有两种方法。一种是使用标识符列表,另一种是使用参数类型列表。标识符列表可以省略,但类型列表不能。因此,假设一个函数在函数定义中不带参数,则使用(省略的)标识符列表
执行此操作void f() {
/* do something ... */
}
这是一个参数类型列表:
void f(void) {
/* do something ... */
}
如果在参数类型列表中只有一个参数类型为void(它必须没有名称),那么这意味着该函数不带参数。但是这两种定义函数的方式在它们声明的内容上有所不同。
第一个定义函数接受特定数量的参数,但是既没有传递计数也没有传递所需的类型 - 就像使用标识符列表的所有函数声明一样。因此呼叫者必须事先知道类型和计数。因此,如果调用者调用函数给它一些参数,则行为是未定义的。例如,堆栈可能会损坏,因为被调用的函数在获得控制权时需要不同的布局。
不推荐在函数参数中使用标识符列表。它在过去使用过,并且仍然存在于许多生产代码中。由于这些参数提升,它们可能会导致严重的危险(如果提升的参数类型与函数定义的参数类型不匹配,行为也未定义!)并且当然不那么安全。所以总是在没有参数的函数中使用void
thingy,仅用于声明和函数定义。
第二个定义该函数接受零参数并且还传达它 - 就像使用参数类型列表声明函数的所有情况一样,称为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);
第一个使用标识符列表定义函数,而第二个使用包含参数类型列表的声明为其提供原型。
答案 2 :(得分:18)
void foo(void)
更好,因为它明确说明:不允许参数。
void foo()
意味着你可以(在某些编译器下)发送参数,至少如果这是你的函数的声明而不是它的定义。
答案 3 :(得分:9)
C99引用
这个答案旨在引用和解释C99 N1256 standard draft的相关部分。
声明者的定义
术语声明者会出现很多,所以让我们理解它。
从语言语法中,我们发现以下下划线字符是声明符:
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
和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函数声明符中不属于a的空列表 该函数的定义指定没有关于数量或类型的信息 提供参数。
所以你可以逃脱:
void f();
void f(int x) {}
其他时候,UB可能会爬升(如果你很幸运,编译器会告诉你),你将很难搞清楚原因:
void f();
void f(float x) {}
请参阅:Why does an empty declaration work for definitions with int arguments but not for float arguments?
定义的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); }
不符合要求,如:Why does gcc allow arguments to be passed to a function defined to be with no arguments?
TODO明白为什么。与原型有关还是与原型有关。定义原型。
答案 4 :(得分:3)
除了语法差异之外,许多人还倾向于使用void function(void)
出于实际原因:
如果你正在使用搜索功能并希望找到该功能的实现,你可以搜索function(void)
,它将返回原型以及实现。
如果省略了第二个void
,则必须搜索function()
,因此也会查找所有函数调用,这使得查找实际实现变得困难。
答案 5 :(得分:1)
在C ++中,main()
和main(void)
存在无差异。
但在C中,main()
将使用任意参数进行调用。
示例:
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)
}
main(void)
将被称为,不带任何参数。如果我们尝试通过,那么最终导致编译器错误。
示例:
main (void) {
main(10,"abc",12.13);
//This throws "error: too many arguments to function ‘main’ "
}