什么更好:void foo()
或void foo(void)
?使用 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
顺便说一句,如果文件同时包含省略的标识符列表和参数类型列表,则参数类型列表“获胜”。最后的函数类型包含一个原型:
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
是f(x, y)
类型。
这是一种较旧的方法,不应再使用。6.11.6 函数声明器说:
1 使用带空括号的函数声明符(不是原型格式参数类型声明符)是一个过时的特性。
简介解释了什么是过时的功能:
某些特性已经过时,这意味着它们可能会在本国际标准的未来修订版中被考虑撤销。由于它们的广泛使用,它们被保留,但不鼓励在新实现(用于实现特性)或新程序(用于语言 [6.11] 或库特性 [7.26])中使用它们
f() vs f(void) 用于声明
当你只写:
void f();
它必须是一个标识符列表声明,因为6.7.5 “Declarators”将语法定义为:
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 参数的定义,但不适用于 float 参数?
f() 和 f(void) 用于定义
f() {}
对比
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 明白为什么。与是否是原型有关。定义原型。
除了句法上的差异,许多人还void function(void)
出于实际原因更喜欢使用:
如果你正在使用搜索功能并想找到该功能的实现,你可以搜索function(void)
,它会返回原型以及实现。
如果省略void
,则必须搜索function()
并因此也会找到所有函数调用,从而更难找到实际的实现。
在 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 a segmentation fault when run)
}
main(void)
将在没有任何参数的情况下调用。如果我们尝试传递它,那么最终会导致编译器错误。
例子:
main (void) {
main(10, "abc", 12.13);
// This throws "error: too many arguments to function ‘main’ "
}