繁体   English   中英

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

[英]Understanding the difference between f() and f(void) in C and C++ once and for all

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

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

情况二

宣言:

void f();

定义:

void f()
{
   //...
}

题:

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

附加问题:

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

更多术语(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

从K&R C时代起,不是原型的声明就是ANSI 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 1c提升为double 因此, f()的定义必须为:

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

参见C99 6.7.6第15段

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

答案1

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

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

“未定义的行为”是“我们不能保证会发生什么”的规范。 也许您的程序会崩溃,也许会正常运行,也许会邀请您的姻亲吃饭。

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

答案2

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

它不应该编译。

例外情况

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

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

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

脚注

1char 可能 char unsigned int ,但这很不常见。

如果您使用的是C99或更高版本,那么整个事情实际上是有争议的(并且,除非您被困在旧的嵌入式系统上或类似的东西,否则您可能应该使用更现代的东西)。

C99 / C11第6.11.6 Future language directions, Function declarators节, 6.11.6 Future language directions, Function declarators指出:

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

因此,您应避免使用void f();类的东西void f(); 共。

如果需要参数,请列出它们,以形成合适的原型。 如果不是,我们void以明确表示它不接受任何参数。

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

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

在纯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

但是在Pure 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM