哪个更好:void foo()或void foo(void)? 使用void时,它看起来很丑且不一致,但有人告诉我它很好。这是真的吗?

编辑:我知道一些旧的编译器做奇怪的事情,但如果我只是使用GCC,是void foo()好吗?将foo (bar);然后被接受?


当前回答

void foo(void);

这是在C中说“无参数”的正确方式,在c++中也适用。

But:

void foo();

在C和c++中意味着不同的东西!在C语言中,它的意思是“可以接受任何数量的未知类型的参数”,在c++中,它的意思与foo(void)相同。

变量参数列表函数本质上是非类型安全的,应该尽可能避免使用。

其他回答

void foo(void);

这是在C中说“无参数”的正确方式,在c++中也适用。

But:

void foo();

在C和c++中意味着不同的东西!在C语言中,它的意思是“可以接受任何数量的未知类型的参数”,在c++中,它的意思与foo(void)相同。

变量参数列表函数本质上是非类型安全的,应该尽可能避免使用。

除了语法上的差异,许多人还出于实际原因更喜欢使用void函数:

如果你正在使用搜索函数并想要找到函数的实现,你可以搜索function(void),它将返回原型以及实现。

如果省略void,则必须搜索function(),因此也将找到所有函数调用,从而使查找实际实现更加困难。

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“函数声明器(包括原型)”说:

函数声明器中不属于该函数定义的标识符列表应为空。

它被称为标识符列表,因为我们只给出f(x, y)上的标识符x和y,后面是类型。

这是一个较老的方法,不应该再使用了。6.11.6函数声明:

1使用带有空括号的函数声明器(而不是原型格式的参数类型声明器)是一个过时的特性。

引言解释了什么是过时的特性:

某些特性是过时的,这意味着它们可能会被考虑 在本国际标准的未来修订中撤回。它们被保留是因为 它们的广泛使用,但它们的使用在新的实现(为实现 特性)或新程序(针对语言[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)。

直接声明器是唯一定义声明器圆括号(…)部分的语法节点。

那么,我们如何消除歧义和使用更好的参数类型列表没有参数?6.7.5.3函数声明符(包括原型)表示:

特殊情况下,void类型的未命名形参作为列表中唯一的项,指定该函数没有形参。

So:

void f(void);

就是这样。

这是一种神奇的语法,因为我们不能以任何其他方式使用void类型参数:

void f(void v);
void f(int i, void);
void f(void, int);

如果我使用f()声明会发生什么?

也许代码会编译得很好:6.7.5.3函数声明器(包括原型):

类的一部分,函数声明器中的空列表 类的数量或类型的信息 提供参数。

所以你可以逃避:

void f();
void f(int x) {}

其他时候,UB可能会爬行(如果你幸运的话,编译器会告诉你),你将很难弄清楚为什么:

void f();
void f(float x) {}

参见:为什么空声明适用于带有int参数的定义,而不适用于float参数?

F()和F (void)用于定义

f() {}

vs

f(void) {}

相似,但不完全相同。

6.7.5.3函数声明符(包括原型)表示:

函数声明器中的空列表是该函数定义的一部分,它指定该函数没有形参。

它看起来类似于f(void)的描述。

但仍…似乎是:

int f() { return 0; }
int main(void) { f(1); }

是遵循未定义的行为,而:

int f(void) { return 0; }
int main(void) { f(1); }

不符合:为什么gcc允许将参数传递给定义为无参数的函数?

TODO完全理解其中的原因。这和是不是原型有关系。定义原型。

在c语言中有两种指定参数的方法,一种是使用标识符列表,另一种是使用参数类型列表。标识符列表可以省略,但类型列表不能省略。所以,说一个函数在函数定义中没有参数,你可以用一个(省略的)标识符列表来做到这一点

void f() {
    /* do something ... */
}

这是一个参数类型列表:

void f(void) {
    /* do something ... */
}

如果在一个形参类型列表中只有一个形参类型是void(它必须没有名称),那么这意味着函数没有实参。但这两种定义函数的方法在声明内容方面存在差异。

标识符列表

第一个定义了函数接受特定数量的参数,但与所有使用标识符列表的函数声明一样,既没有传达计数,也没有传递所需参数的类型。因此,调用者必须事先准确地知道类型和计数。因此,如果调用者在调用函数时给出一些参数,则该行为是未定义的。例如,堆栈可能被破坏,因为被调用的函数在获得控制权时期望不同的布局。

不建议在函数参数中使用标识符列表。它在过去被使用,并且仍然存在于许多生产代码中。由于这些参数提升,它们可能会导致严重的危险(如果提升的参数类型与函数定义的形参类型不匹配,则行为也未定义!),当然,它们也不太安全。所以对于没有形参的函数,无论在函数的声明还是定义中,都要使用void。

参数类型列表

第二个定义函数接受零参数,并与所有使用形参类型列表(称为原型)声明函数的情况一样进行通信。如果调用者调用函数并给它一些参数,这是一个错误,编译器会吐出一个适当的错误。

The second way of declaring a function has plenty of benefits. One of course is that amount and types of parameters are checked. Another difference is that because the compiler knows the parameter types, it can apply implicit conversions of the arguments to the type of the parameters. If no parameter type list is present, that can't be done, and arguments are converted to promoted types (that is called the default argument promotion). char will become int, for example, while float will become 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()意味着你可以(在某些编译器下)发送参数,至少如果这是你的函数的声明而不是它的定义。