一劳永逸地理解 C 和 C 中 f() 和 f(void) 的区别

新手上路,请多包涵

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

对于 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 带有参数,但在没有参数的情况下定义它,会有所不同吗?我应该能够解决函数体中的参数吗?

原文由 user500944 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 954
1 个回答

更多术语(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 函数声明器

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

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

怎么了

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

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

 void f();

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

在这个程序中, a 被提升为 int 1和 c 被提升为 double 所以 f() 的定义必须是:

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

见 C99 6.7.6 第 15 段,

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

答案 1

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

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

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

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

答案 2

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

它不应该编译。

例外

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

  1. 可以将 char * 传递给需要 void * --- 的函数,反之亦然。

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

脚注

1 : 有 可能 charunsigned int ,但这种情况非常少见。

原文由 Dietrich Epp 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题