彻底理解C和C++中f()和f(void)之间的区别

54

好的,所以我听到了不同的观点,只是想确保我正确理解了它。

对于C++

声明void f();void f(void);精确地意味着相同的事情,函数f不带任何参数。定义也是如此。

对于C

声明void f(void);表示f不带任何参数。

声明void f();表示函数f可能有参数,也可能没有参数,如果有参数,我们不知道这些参数是什么类型,或者有多少个。请注意,这与省略号不同,我们不能使用va_list

现在事情变得有趣了。

情况1

声明:

void f();

定义:

void f(int a, int b, float c)
{
   //...
}

案例2

声明:

void f();

定义:

void f()
{
   //...
}

问题:

在情况1和2中,当我们使用正确参数、错误参数或根本没有参数调用f时,编译时会发生什么?运行时又会发生什么?

附加问题:

如果我声明了带有参数的f,但不带参数定义它,会有什么区别吗?我能够在函数体中处理这些参数吗?


1
在C语言中,void f();void f(void);是相同的。前者隐含地表示“没有参数”(编译器会推断),而后者明确地表示“没有参数”。此外,最好自己尝试这些示例,以便在以后使用编译器时更加熟练。 - yeyo
@Dietrich(这是我到现在为止不知道的事情,谢谢),但正如paxdiablo所指出的那样,这只发生在声明时,我是指在_定义_函数时。哦,好吧,http://pastebin.com/MqhDBZg1 这个链接是一个程序,有人可以试着理解一下它(就像我一样:)) - yeyo
据我所知,在C99中,void f(); 和 void f(void); 是相同的;如果我错了,请纠正我。 - martinkunev
@paxdiablo 的 void f(); 不是原型(它是一个声明,而不是原型)。 - M.M
4个回答

62

更多术语(C,非 C++):函数的原型声明其参数的类型。否则,该函数没有原型。

void f();                      // Declaration, but not a prototype
void f(void);                  // Declaration and prototype
void f(int a, int b, float c); // Declaration and prototype

非原型声明是ANSI C之前的K&R C时代的残留物。使用旧式声明的唯一原因是与旧代码保持二进制兼容性。例如,在GTK 2中,有一个没有原型的函数声明--它是意外出现的,但是如果删除它会破坏二进制文件。C99标准注释:

6.11.6函数声明符

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

建议:我建议在GCC/Clang中使用-Wstrict-prototypes-Wmissing-prototypes编译所有C代码,除了通常的-Wall -Wextra之外。

会发生什么

void f(); // declaration
void f(int a, int b, float c) { } // ERROR

声明与函数体不一致!这实际上是一个编译时错误,因为在没有原型的函数中不能有浮点参数。无法在未经过原型声明的函数中使用浮点类型,因为当您调用此类函数时,所有参数都会使用默认升级进行升级。以下是修复后的示例:

void f();

void g()
{
    char a;
    int b;
    float c;
    f(a, b, c);
}

在这个程序中,a 被提升为 int1,而 c 则被提升为 double。因此,f() 的定义必须是:

void f(int a, int b, double c)
{
    ...
}
参见C99 6.7.6第15段,如果一个类型具有参数类型列表,而另一个类型由不是函数定义的函数声明符指定,并包含一个空标识符列表,则参数列表不得具有省略号终止符,每个参数的类型都必须与应用默认参数提升所得到的类型兼容。

“What happens” 是完全错误的 - 你不会收到错误,也不应该收到。 - paxdiablo
@paxdiablo:但是我确实遇到了一个错误。GCC:错误:‘f’的类型冲突;注意:具有默认提升的参数类型无法匹配空参数名称列表声明。 - Dietrich Epp
@paxdiablo:在GCC 4.0、4.2和4.7以及Clang 3.1中出现错误。 - Dietrich Epp
2
你应该更仔细地阅读错误信息。它与空列表本身无关。它与你选择的参数有关(浮点数)。它不允许具有默认提升(float->double)的参数。如果你将那个浮点数改为整数或双精度,你会发现错误消失了。 - paxdiablo
1
@paxdiablo: 不好意思,我以为我刚才说的就是那个。看看第二个例子,它将类型改为 double - Dietrich Epp
这样就好了,你让错误的原因变得更加清晰了。也许我误解了你最初的观点。 - paxdiablo

7
如果您使用的是C99或更高版本(除非您被困在旧的嵌入式系统之类的东西上,否则您可能应该使用更现代的东西),整个问题实际上就是一个无关紧要的问题。C99/C11第6.11.6节“未来语言方向、函数声明符”指出:

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

因此,您应该完全避免使用像 void f(); 这样的东西。如果需要参数,请列出它们,形成一个正确的原型。如果没有参数,请使用 void 明确表示它不需要任何参数。

3

在C++中,f()和f(void)是相同的。

在C中,它们是不同的,可以在调用f()函数时传递任意数量的参数,但是在f(void)中不能传递任何参数。


-2
在纯C中,这会导致错误:error C2084: function 'void __cdecl f(void )' already has a body
void f(void);
void f();

int main() {
  f(10);
  f(10.10);
  f("ten");

  return 0;
}

void f(void) {

}

void f() {

}

fvoid.c 第19行:错误 C2084:函数 'void __cdecl f(void )' 已经有一个主体。
但是在 C++ 中,它将编译而不会出现错误。 函数重载(仅限于C++,C语言没有函数重载) 通过在同一作用域中声明多个名称为f的函数来重载函数名f。f的声明必须通过参数列表中的类型和/或数量来彼此区分。当您调用名为f的重载函数时,通过将函数调用的参数列表与具有名称f的每个重载候选函数的参数列表进行比较来选择正确的函数。
例如:
#include <iostream>
using namespace std;

void f(int i);
void f(double  f);
void f(char* c);


int main() {
  f(10);
  f(10.10);
  f("ten");

  return 0;
}

void f(int i) {
  cout << " Here is int " << i << endl;
}
void f(double  f) {
  cout << " Here is float " << f << endl;
}

void f(char* c) {
  cout << " Here is char* " << c << endl;
}

输出:

Here is int 10
Here is float 10.1
Here is char* ten

1
那么,这与 void f();void f(void); 之间的差异有什么关系? - Dietrich Epp
void f(void) 是在 C 语言中表示“无参数”的正确方式。但是 void f( ) 也可以正常工作。 - Software_Designer
2
这个问题主要是关于C语言的,不是吗?C语言没有重载。 - GManNickG
但是在C中,void f()同样可以完美运行。 - Mat
在纯C中,void f();和void f(void)之间的区别会导致错误:error C2084: function 'void __cdecl f(void )' already has a body。而在C++中,这只是函数重载的问题。 - Software_Designer
第一个程序在C++中有几个编译错误。第二个程序与问题无关。 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接