使用C void参数“void foo(void)”还是不是“void foo()”更好?
什么更好: void foo()
或void foo(void)
? 无效看起来丑陋和不一致,但我被告知这是好的。 这是真的?
编辑:我知道一些旧的编译器做奇怪的事情,但如果我只使用GCC,是void foo()
好吗? 将foo(bar);
那么被接受?
void foo(void);
这是在C中说“无参数”的正确方法,它也适用于C ++。
但:
void foo();
在C和C ++中意味着不同的东西! 在C中,它意味着“可以使用任意数量的未知types的参数”,而在C ++中,它的意思与foo(void)
相同。
variables参数列表函数本质上是非types安全的,应尽可能避免。
在C中指定参数有两种方法。一种使用标识符列表,另一种使用参数types列表。 标识符列表可以省略,但types列表不能。 所以,要说一个函数在函数定义中不带任何参数,可以用一个(省略的)标识符列表来完成
void f() { /* do something ... */ }
而这与一个参数types列表:
void f(void) { /* do something ... */ }
如果在参数types列表中只有一个参数types是void(那么它必须没有名字),那么这意味着函数不带参数。 但是定义一个函数的这两种方法在他们声明的内容上有所不同。
标识符列表
第一个定义是该函数接受了一个特定数量的参数,但是这个计数并没有被传递,也没有需要的types – 就像所有使用标识符列表的函数声明一样。 所以来电者必须事先知道types和数量。 所以如果调用者调用给它一些参数的函数,行为是不确定的。 例如,堆栈可能会被破坏,因为被调用函数在获得控制权时期望有不同的布局。
在函数参数中使用标识符列表已被弃用。 它在过去被使用,并且仍然存在于大量的生产代码中。 由于这些参数提升(如果提升的参数types与函数定义的参数types不匹配,行为也是未定义的),它们可能会造成严重的危险,当然,它们的安全性要低得多。 因此,在函数的唯一声明和定义中,总是使用void
thingy作为不带参数的函数。
参数types列表
第二个定义是该函数接受零参数,并且也传达了这个信息,就像所有使用被称为prototype
的参数types列表声明函数的情况一样。 如果调用者调用该函数并给出一些参数,那是一个错误,编译器会吐出一个适当的错误。
声明函数的第二种方法有很多好处。 其中之一是检查参数的数量和types。 另一个区别是,因为编译器知道参数types,它可以将参数的隐式转换应用于参数的types。 如果不存在任何参数types列表,则不能完成,并将参数转换为提升types(称为默认参数提升)。 例如, char
将变成int
,而float
将变成double
。
复合types的function
顺便提及,如果文件同时包含省略的标识符列表和参数types列表,则参数types列表“胜出”。 最后的函数types包含一个原型:
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);
第一个使用标识符列表定义函数,第二个使用包含参数types列表的声明为其提供原型。
void foo(void)
更好,因为它明确地表示:不允许任何参数。
void foo()
意味着你可以(在一些编译器下)发送参数,至less如果这是你的函数的声明而不是它的定义。
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; } ^^^^^^^
声明符是函数声明和定义的一部分。
有两种types的声明符:
- 参数types列表
- 标识符列表
参数types列表
声明如下所示:
int f(int x, int y);
定义如下所示:
int f(int x, int y) { return x + y; }
它被称为参数types列表,因为我们必须给出每个参数的types。
标识符列表
定义如下所示:
int f(x, y) int x; int y; { return x + y; }
声明如下所示:
int g();
我们不能用一个非空的标识符列表声明一个函数:
int g(x, y);
因为6.7.5.3“函数声明器(包括原型)”说:
3函数声明符中不是该函数定义的标识符列表应该是空的。
它被称为标识符列表,因为我们只给f(x, y)
上的标识符x
和y
,types来了。
这是一个较旧的方法,不应再使用。 6.11.6函数声明符说:
1具有空括号的函数声明符(不是原型格式参数types声明符)的使用是过时的特性。
介绍解释什么是过时的function :
某些function已过时,这意味着在本标准的未来版本中可能会考虑退出。 由于它们的广泛使用,它们被保留下来,但不鼓励它们在新实现(用于实现function)或新程序(用于语言[6.11]或库特征[7.26])中的使用
f()与f(void)的声明
当你写:
void f();
它必然是一个标识符列表声明,因为6.7.5“声明符”表示将语法定义为:
direct-declarator: [...] direct-declarator ( parameter-type-list ) direct-declarator ( identifier-list_opt )
所以只有标识符列表版本可以是空的,因为它是可选的( _opt
)。
direct-declarator
是定义direct-declarator
符的括号(...)
部分的唯一语法节点。
那么我们如何消除歧义并使用没有参数的更好的参数types列表呢? 6.7.5.3函数声明符(包括原型)说:
10types为void的未命名参数的特例是列表中唯一的项目,指定该函数没有参数。
所以:
void f(void);
就是这样。
这是一个明确允许的魔法语法,因为我们不能以任何其他方式使用void
types的参数:
void f(void v); void f(int i, void); void f(void, int);
如果我使用f()声明会发生什么?
也许代码会编译得很好: 6.7.5.3函数声明器(包括原型) :
14函数声明符中的空列表不是该函数定义的一部分,指定不提供有关参数数量或types的信息。
所以你可以逃避:
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允许将parameter passing给定义为无参数的函数?
TODO明白为什么。 与做或不做原型有关。 定义原型。
除了语法上的差异之外,很多人还喜欢使用void function(void)
如果你正在使用searchfunction,并希望find函数的实现,你可以searchfunction(void)
,它将返回原型以及实现。
如果省略了第二个void
,则必须searchfunction()
,因此也会查找所有的函数调用,使其难以find实际的实现。
在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' " }