使用C void参数"void foo(void)"或不使用"void foo()"更好吗?

Zif*_*fre 200 c void

什么是更好的:void foo()void foo(void)?随着虚空,它看起来丑陋和不一致,但我被告知它是好的.这是真的?

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

Dan*_*ker 238

void foo(void);
Run Code Online (Sandbox Code Playgroud)

这是在C中说"无参数"的正确方法,它也适用于C++.

但:

void foo();
Run Code Online (Sandbox Code Playgroud)

在C和C++中意味着不同的东西!在C中它意味着"可以采用任意数量的未知类型的参数",而在C++中它意味着相同foo(void).

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

  • 注意void foo(); 并不意味着varargs功能; 它只是意味着编译器没有被告知它的args是什么.varargs函数必须(根据标准)用省略号声明.另外,void foo(){...} - 函数定义而不是声明 - 是正常的(虽然一致性建议也使用void foo(void){...}). (59认同)
  • 他说编译器没有被告知参数的数量/类型.但是你说这个函数可以使用任意数量的未知类型的参数.但这就是`...`(varargs函数).这听起来有点令人困惑,但我想你想说"冷采取一定数量的论点,但这个数字是未知的".它是这样的:你有一个锅,不知道它可以采取多少升.但这并不意味着锅可以采取任何数量的升.在一定量,它只是溢出:)所以对于`void foo()`:对于一些金额/类型,它只是导致未定义的行为 (9认同)
  • 你说“编译器没有被告知它的参数是什么”,我说“它可以接受任意数量的未知类型的参数”。有什么不同? (4认同)
  • `()`在C99中被声明为过时功能.我认为这里出现的唯一混淆是因为我提到vararg列表在讨论空参数列表后立即不安全.这只是因为我想象有人在阅读解释,然后想着"哦,我想知道我能用这个功能实现什么惊人的东西?" 接下来他们会发现的是使用```来编写像'printf`这样的函数,我想立刻劝阻它.这并不是说`()`是做varargs的方法,我说最好完全避免varargs. (3认同)
  • 在我看来,虚空foo(void); 很容易与void foo混淆(void*); ,而foo()不太可能与foo(void*)混淆; (2认同)
  • @DanielEarwicker:自 ANSI C89 以来,非原型函数声明已过时。(这意味着它们可能会在未来的标准中被删除,但即使是 C11 也将​​它们保留在原处 - 不幸的是,恕我直言。) (2认同)

Joh*_*itb 94

在C中指定参数有两种方法.一种是使用标识符列表,另一种是使用参数类型列表.标识符列表可以省略,但类型列表不能.因此,假设一个函数在函数定义中不带参数,则使用(省略的)标识符列表执行此操作

void f() {
    /* do something ... */
}
Run Code Online (Sandbox Code Playgroud)

这有一个参数类型列表:

void f(void) {
    /* do something ... */
}
Run Code Online (Sandbox Code Playgroud)

如果在参数类型列表中,只有一个参数类型为void(它必须没有名称),那么这意味着该函数不带参数.但是这两种定义函数的方式在它们声明的内容上有所不同.

标识符列表

第一个定义函数采用特定数量的参数,但既不传递计数也不传递所需的类型 - 就像使用标识符列表的所有函数声明一样.因此呼叫者必须事先知道类型和计数.因此,如果调用者调用函数给它一些参数,则行为是未定义的.例如,堆栈可能会损坏,因为被调用的函数在获得控制时需要不同的布局.

不推荐在函数参数中使用标识符列表.它在过去使用过,并且仍然存在于许多生产代码中.由于这些参数提升,它们可能会导致严重的危险(如果提升的参数类型与函数定义的参数类型不匹配,行为也未定义!)并且当然不那么安全.所以总是在void没有参数的函数中使用thingy,仅用于声明和函数定义.

参数类型列表

第二个定义该函数接受零参数并且还传达它 - 就像使用参数类型列表声明函数的所有情况一样,它被称为a prototype.如果调用者调用该函数并给它一些参数,那就是一个错误,并且编译器会发出一个适当的错误.

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

函数的复合类型

顺便说一下,如果文件包含省略的标识符列表和参数类型列表,则参数类型列表"wins".最后的函数类型包含一个原型:

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

// f has now a prototype. 
Run Code Online (Sandbox Code Playgroud)

那是因为两个声明都没有说出任何矛盾的说法.然而,第二个还有话要说.这是一个被接受的论点.反过来也可以这样做

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

void f(int);
Run Code Online (Sandbox Code Playgroud)

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


Uri*_*Uri 18

void foo(void) 更好,因为它明确地说:不允许参数.

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


小智 10

在 C++ 中,和没有区别。main()main(void)

\n

在 C 中,main()将使用任意数量的参数进行调用。

\n

例子:

\n
main (){\n    main(10, "abc", 12.28);\n    // Works fine!\n    // It won't give the error. The code will compile successfully.\n    // (May cause a segmentation fault when run)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

main(void)将在没有任何参数的情况下调用。如果我们尝试传递它,那么最终会导致编译器错误。

\n

例子:

\n
main (void) {\n     main(10, "abc", 12.13);\n     // This throws "error: too many arguments to function \xe2\x80\x98main\xe2\x80\x99 "\n}\n
Run Code Online (Sandbox Code Playgroud)\n

  • 注意:对于 C++,这两个片段总是格式错误的,因为 C++ 禁止调用 main 或获取其地址。 (3认同)

Cir*_*四事件 9

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; }
    ^^^^^^^
Run Code Online (Sandbox Code Playgroud)

声明符是函数声明和定义的一部分.

有两种类型的声明符:

  • 参数类型列表
  • 标识符列表

参数类型列表

声明如下:

int f(int x, int y);
Run Code Online (Sandbox Code Playgroud)

定义如下:

int f(int x, int y) { return x + y; }
Run Code Online (Sandbox Code Playgroud)

它被称为参数类型列表,因为我们必须给出每个参数的类型.

标识符列表

定义如下:

int f(x, y)
    int x;
    int y;
{ return x + y; }
Run Code Online (Sandbox Code Playgroud)

声明如下:

int g();
Run Code Online (Sandbox Code Playgroud)

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

int g(x, y);
Run Code Online (Sandbox Code Playgroud)

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

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

它被称为标识符列表,因为我们只提供标识符xyon f(x, y),类型后来.

这是一种较旧的方法,不应再使用了.6.11.6函数声明符说:

1使用带有空括号的函数声明符(不是prototype-format参数类型声明符)是一个过时的功能.

简介解释了什么是过时的功能:

某些功能已过时,这意味着它们可能会在本国际标准的未来版本中考虑撤销.由于它们的广泛使用,它们被保留,但是它们在新实现(用于实现功能)或新程序(用于语言[6.11]或库功能[7.26])中的使用不鼓励使用

f()vs f(void)用于声明

当你写的时候:

void f();
Run Code Online (Sandbox Code Playgroud)

它必然是一个标识符列表声明,因为6.7.5"声明者"说将语法定义为:

direct-declarator:
    [...]
    direct-declarator ( parameter-type-list )
    direct-declarator ( identifier-list_opt )
Run Code Online (Sandbox Code Playgroud)

所以只有标识符列表版本可以为空,因为它是可选的(_opt).

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

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

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

所以:

void f(void);
Run Code Online (Sandbox Code Playgroud)

就是这样.

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

void f(void v);
void f(int i, void);
void f(void, int);
Run Code Online (Sandbox Code Playgroud)

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

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

14函数声明符中不属于该函数定义的空列表指定不提供有关参数数量或类型的信息.

所以你可以逃脱:

void f();
void f(int x) {}
Run Code Online (Sandbox Code Playgroud)

其他时候,UB可以爬起来(如果你很幸运,编译器会告诉你),你将很难搞清楚原因:

void f();
void f(float x) {}
Run Code Online (Sandbox Code Playgroud)

请参阅:为什么空声明适用于带有int参数的定义但不适用于浮点参数?

定义的f()和f(void)

f() {}
Run Code Online (Sandbox Code Playgroud)

VS

f(void) {}
Run Code Online (Sandbox Code Playgroud)

是相似的,但不完全相同.

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

14函数声明符中的空列表是该函数定义的一部分,指定该函数没有参数.

看起来类似于描述f(void).

仍然......似乎:

int f() { return 0; }
int main(void) { f(1); }
Run Code Online (Sandbox Code Playgroud)

符合未定义的行为,同时:

int f(void) { return 0; }
int main(void) { f(1); }
Run Code Online (Sandbox Code Playgroud)

是不符合的,如下所述:为什么gcc允许将参数传递给定义为不带参数的函数?

TODO明白为什么.与原型有关还是与原型有关.定义原型.


maj*_*aja 6

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

如果您正在使用搜索功能并想找到该功能的实现,您可以搜索function(void),它会返回原型以及实现。

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