哪个更好:void foo()void foo(void)
带有空白的外观看起来很丑陋和不一致,但是有人告诉我它很好。

编辑:我知道有些旧的编译器做的很奇怪,但是如果我仅使用GCC,可以吗?会接受void foo()吗?

#1 楼

void foo(void);


这是在C语言中说“无参数”的正确方法,它也适用于C ++。

但是: br />
表示C和C ++中的不同内容!在C中,它表示“可以接受任意数量的未知类型的参数”;在C ++中,它表示与foo(void)相同。

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

评论


注意void foo();不代表varargs函数;这只是意味着没有告知编译器其args是什么。必须根据省略号声明varargs函数(根据标准)。另外,void foo(){...}-函数定义而不是声明-是可以的(尽管一致性建议在这里也使用void foo(void){...})。

–乔纳森·莱弗勒(Jonathan Leffler)
2009年6月20日14:05

您说“尚未告知编译器其args是什么”,我说“它可以接受任意数量的未知类型的参数”。有什么不同?

–丹尼尔(Daniel Earwicker)
09年6月20日在15:41

他说,尚未告知编译器参数的数量/类型。但是您说函数可以接受任意数量的未知类型的参数。但这就是...的作用(varargs函数)。这听起来有点令人困惑,但是我想您想说“冷带一定数量的参数,但是这个数量未知”。就像这样:您有一个锅,不知道它可以喝多少公升。但这并不意味着该锅可以装任何数量的水。在某种程度上,它只会溢出:)所以对于void foo():对于某些数量/类型,它只会导致未定义的行为

– Johannes Schaub-小人
09年7月22日在10:35

()在C99中被声明为过时功能。我认为这里唯一的混乱是因为我提到在讨论空参数列表后立即提到vararg列表是不安全的。这仅仅是因为我想象有人阅读了说明然后想到:“哦,我想知道使用此功能可以实现哪些惊人的功能?”然后,他们会发现的下一件事情是使用...编写诸如printf之类的函数,而我想立即阻止这种行为。我并不是说()是做varargs的方法,而是说最好完全避免使用varargs。

–丹尼尔(Daniel Earwicker)
09年7月22日在11:52

在我看来void foo(void);容易与void foo(void *)混淆; ,而foo()不太可能与foo(void *)混淆;

–ArtOfWarfare
2012年10月8日在20:16

#2 楼

在C中有两种指定参数的方法。一种是使用标识符列表,另一种是使用参数类型列表。标识符列表可以省略,但类型列表不能。因此,要说一个函数在函数定义中不接受任何参数,可以使用(省略的)标识符列表进行操作

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


并使用参数类型列表:

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


如果在参数类型列表中只有一个参数类型为void(则它必须没有名称),则意味着该函数不接受任何参数。但是这两种定义函数的方式在声明内容方面有所不同。

标识符列表

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

不建议在函数参数中使用标识符列表。它过去曾经使用过,现在仍然存在于许多生产代码中。由于这些参数提升,它们可能会导致严重的危险(如果提升的参数类型与函数定义的参数类型不匹配,则行为也未定义!),并且安全性当然要低得多。因此,对于仅在函数的声明和定义中都没有参数的函数,请始终使用void方法。

参数类型列表

第二个参数定义该函数接受零个参数,并且还进行通信(与所有使用参数类型列表声明该函数的情况一样),这称为prototype。如果调用者调用该函数并为其提供了一些参数,则表示错误,编译器将吐出适当的错误。

声明函数的第二种方法有很多好处。当然之一是检查参数的数量和类型。另一个区别是,由于编译器知道参数类型,因此可以将参数的隐式转换应用于参数的类型。如果不存在任何参数类型列表,则无法完成,参数将转换为提升的类型(称为默认参数提升)。例如,char将变为int,而float将变为double

函数的复合类型

顺便说一句,如果文件同时包含省略的标识符列表和参数类型列表,则参数类型列表“胜出”。最后的函数类型包含一个原型:

void f();
void f(int a) {
    printf("%d", a);
}

// f has now a prototype. 


那是因为两个声明都没有说矛盾的地方。但是,第二个要说的是另外的话。那是一个论点被接受。可以反向执行相同的操作

第一个使用标识符列表定义函数,第二个使用包含参数的声明为其提供原型。类型列表。

评论


如何使用Astyle之类的代码格式化工具来更改程序中不带接受参数的所有函数以使myfunction(void)无效?我可以通过astyle给所有功能更改任何选项吗?

–user7375520
17年2月4日在19:41

@ user7375520将gcc与-Wstrict-prototypes一起使用

– ljrk
17年11月3日在20:52

#3 楼

void foo(void)更好,因为它明确指出:不允许使用参数。

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

#4 楼

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();


我们不能用非空标识符列表声明函数:

q4 312078q

,因为6.7.5.3“函数声明符(包括原型)”说:


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


它被称为标识符列表,因为我们仅在x上给出标识符yf(x, y),类型紧随其后。一种较旧的方法,不应再使用。 6.11.6函数声明符说:


1使用带有空括号的函数声明符(不是原型格式的参数类型声明符)是过时的功能。


和引言中解释了什么是过时的功能:标准。之所以保留它们,是因为
它们的广泛使用,但是它们在新的实现中使用(用于实现
功能)或新程序(对于语言[6.11]或库功能[7.26])不推荐使用


f()与f(void)进行声明

当您只写:

int g(x, y);


它肯定是一个标识符列表声明,因为6.7.5“ Declarators”说将语法定义为:

void f();


,因此只有标识符列表版本可以为空,因为它是可选的(_opt)。声明符。

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


10唯一类型为void的未命名参数的特殊情况是列表中的唯一项,表明函数没有参数。 br />

所以:

direct-declarator:
    [...]
    direct-declarator ( parameter-type-list )
    direct-declarator ( identifier-list_opt )


就是这样。

这是一种明确允许的魔术语法,因为我们不能以其他任何方式使用direct-declarator类型的参数:

void f(void);


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

也许代码可以很好地编译:6.7.5.3函数声明符(包括原型):


14函数声明符中的空列表不属于
的定义该函数指定不提供有关
参数的数量或类型的信息。 br />
其他时候,UB会逐渐发展(如果幸运的话,编译器会告诉您),并且您将很难找出原因: />
void f(void v);
void f(int i, void);
void f(void, int);


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

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

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


vs

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


相似,但不完全相同。 />6.7.5.3函数声明符(包括原型)说:


14作为该函数定义的一部分的函数声明器中的空列表表示该函数没有参数。


(...)的描述。

但仍然...似乎: br />
f() {}


不符合如下所述:为什么gcc允许将参数传递给定义为不带参数的函数?是否与原型有关。定义原型。

评论


从open-std.org/jtc1/sc22/wg14/www/docs/dr_317.htm中,我得出结论,void f(){}没有[参数]列表,因此当然也没有6.7.5.3要求的空列表14。但是然后我不知道什么是空参数列表...

– ljrk
17年11月3日在21:20

好的,没有空的参数列表之类的东西。就是函数定义的函数声明器中的空列表未指定任何参数。但是,编译此代码不需要诊断,因为以前的K&R代码是用这种方式定义的。但是,如果所有代码路径都归结为以错误的参数(基本上是运行时错误)调用该函数,那将是UD。如果存在原型指定参数或所有代码路径均为UD的情况,则使用错误的参数调用函数仅是编译时错误。

– ljrk
17年11月4日在11:58

#5 楼

除了语法上的差异外,出于实际原因,许多人还更喜欢使用void function(void)
如果您正在使用搜索功能并想找到该功能的实现,则可以搜索function(void),它也会返回原型作为实现。
如果省略void,则必须搜索function(),因此还将找到所有函数调用,这使得查找实际实现更加困难。

#6 楼

在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’ "
}