是否更好地使用C void参数"或者不是“void foo()”?

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

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

151263 次浏览

void foo(void)更好,因为它显式地说:不允许有参数。

void foo()意味着你可以(在某些编译器下)发送参数,至少如果这是你的函数的声明而不是它的定义。

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之类的东西。

参数类型列表

第二个定义函数接受0个参数,并与所有使用形参类型列表(称为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 function(void):

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

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

C99报价

这个答案旨在引用和解释c99n1256标准草案的相关部分。

声明器的定义

说明符这个词会经常出现,所以让我们来理解它。

从语言语法中,我们发现下面的下划线字符是声明符:

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)上给出标识符xy,类型在后面。

这是一个较老的方法,不应该再使用了。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)。

direct-declarator是唯一定义声明器圆括号(...)部分的语法节点。

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

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

所以:

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++中,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’ "
}