52

好的,所以我听到了关于这个主题的不同意见,只是想确保我理解正确。

对于 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()
{
   //...
}

问题:

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

附加问题:

如果我f用参数声明,但没有参数定义它,会有所不同吗?我应该能够解决函数体中的参数吗?

4

4 回答 4

61

更多术语(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 中编译所有 C 代码-Wstrict-prototypes-Wmissing-prototypes除了通常的-Wall -Wextra.

发生什么了

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

声明与函数体不一致!这实际上是一个编译时错误,这是因为在没有原型的函数中不能有float参数。您不能float在非原型函数中使用 a 的原因是,当您调用此类函数时,所有参数都会使用某些默认提升来提升。这是一个固定的例子:

void f();

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

在这个程序中,a被提升为int1c被提升为double. 所以 for 的定义f()必须是:

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

见 C99 6.7.6 第 15 段,

如果一种类型具有参数类型列表,而另一种类型由不属于函数定义的函数声明符指定且包含空标识符列表,则参数列表不应有省略号终止符,并且每个参数的类型应与应用默认参数提升所产生的类型兼容。

答案 1

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

当您调用f()时,参数会使用默认促销来提升。如果提升的类型与 的实际参数类型匹配f(),那么一切都很好。如果它们不匹配,它可能会编译,但你肯定会得到未定义的行为。

“未定义的行为”是规范的说法,即“我们不保证会发生什么”。也许你的程序会崩溃,也许它会正常工作,也许它会邀请你的姻亲来吃饭。

有两种方法可以在编译时获得诊断。如果您有一个具有跨模块静态分析功能的复杂编译器,那么您可能会收到一条错误消息。您还可以使用 GCC 获取非原型函数声明的消息,使用-Wstrict-prototypes-- 我建议在您的所有项目中打开它(使用 GTK 2 的文件除外)。

答案 2

如果我f用参数声明,但没有参数定义它,会有所不同吗?我应该能够解决函数体中的参数吗?

它不应该编译。

例外

实际上有两种情况允许函数参数与函数定义不一致。

  1. 可以传递char *给期望的函数,void *反之亦然。

  2. 可以将有符号整数类型传递给期望该类型的无符号版本的函数,反之亦然,只要该值在两种类型中都是可表示的(即,它不是负数,并且不超出签名类型)。

脚注

1:有可能char提升到,unsigned int但这很不常见。

于 2012-11-10T05:33:38.863 回答
7

如果您使用的是 C99 或更高版本(并且,除非您被困在旧的嵌入式系统或类似的东西上,否则您可能应该使用更现代的东西)。

C99/C11 部分6.11.6 Future language directions, Function declarators规定:

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

因此,您应该避免void f();完全使用类似的东西。

如果它需要参数,列出它们,形成一个适当的原型。如果不是,请使用void明确表示它不接受任何参数。

于 2012-11-10T05:45:38.310 回答
3

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

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

于 2012-11-10T10:06:38.907 回答
-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 line(19) : error C2084: function 'void __cdecl f(void )' already has a body

但在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
于 2012-11-10T06:09:40.537 回答