【问题标题】:Why is a function prototype not needed in C if there is no return in a function defined after main()?如果在 main() 之后定义的函数中没有返回,为什么 C 中不需要函数原型?
【发布时间】:2020-01-06 12:54:52
【问题描述】:

如果原型double cubenum(); 未在 C 中按要求声明,则如预期的那样,以下代码会生成错误。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("Answer is: %f", cubenum(3.0));
    return 0;
}

double cubenum(double number){
    double result = number * number * number;
    return result;
}

而如果上面的 cubenum 定义被替换为以下定义而不返回,那么当未声明 cubenum 原型时它不会产生任何错误:

void cubenum(double number){
    double result = number * number * number;
    printf("Answer is: %f", result);
}

当原型被声明为 void cubenum();上面的 cubenum 定义没有返回它会产生以下错误:

||=== Build: Debug in xxx(compiler: GNU GCC Compiler) ===|
C:\xxx\main.c||In function 'main':|
C:\xxx\main.c|10|error: invalid use of void expression|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

第 10 行在测试时为:printf("Answer is: %f", cubenum(3.0));

所以,问题是:

为什么没有返回的函数,原型声明是不需要的,如果在上面的例子中声明会出错?

GCC 版本信息

gcc (MinGW.org GCC-6.3.0-1) 6.3.0

【问题讨论】:

  • 关于最后一个错误,不返回值的函数的返回值怎么用? (比如,如果cubenum 没有返回任何内容,printf("Answer is: %f", cubenum(3.0)) 会打印什么?)
  • @Ayxan 有关系,但不会回答这个问题。
  • @Ayxan 如果我没看错,这与前向声明无关。 OP 询问为什么最后一个不会像第一个那样产生错误。 OP 预计会出现错误,而不是相反。
  • @Ayxan 请再读一遍我说的话。是的,从技术上讲,它是相对于 C2011 的扩展,但只要它是所有三个最常用的 C 编译器都接受的默认编译模式的扩展,每个不是语言律师的人自然会认为它是C语言的一部分,告诉他们它不是只会让他们感到困惑。迂腐是一种糟糕的教学法。

标签: c gcc


【解决方案1】:

您编写程序的方式是非常古老 C 程序的特征,可追溯到 1980 年代或 1990 年代,在“原型”函数声明成为首选风格之前。直到今天,C 编译器仍在竭力让那些非常古老的程序继续工作,保留它们所依赖的语言特性,但这些特性从未被标准化,或者自 1989 年以来已从 C 标准中删除。

第一个程序完美正确的现代风格应该是这样的:

#include <stdio.h>

double cubenum(double);

int main(void)
{
    printf("Answer is: %f", cubenum(3.0));
    return 0;
}

double cubenum(double number)
{
    double result = number * number * number;
    return result;
}

带有cubenumdouble cubenum(double);原型前向声明

重要的是要了解double cubenum(); 不是C 中的原型声明,而是说cubenum 接受任何数量和类型 参数的声明。如果您想指定 cubenum 接受 no 参数,则必须编写 double cubenum(void); 这也是我将 int main() 更改为 int main(void) 的原因。

当您完全忽略前向声明时,

#include <stdio.h>

int main(void)
{
    printf("Answer is: %f", cubenum(3.0));
    return 0;
}

double cubenum(double number)
{
    double result = number * number * number;
    return result;
}

C 编译器看到一个对 cubenum 的调用,而之前根本没有声明它。这在我提到的那些非常古老的 C 程序中很常见。他们依赖于一个名为隐式声明的特性,它是原始 C 标准的一部分,但从 1999 年的修订版(通常称为“C99”)中删除。基本上,编译器假定程序员打算将int cubenum(); 写在main 之上,但懒惰地忽略了它。这意味着cubenum 接受任何数量和类型 的参数(不是不接受任何参数)并返回int。所以,暂时不考虑main,就像你写的一样

int cubenum();
double cubenum(double number) { ... }

并且编译器拒绝该程序,因为cubenum 的定义与(隐式)前向声明具有不同的返回类型。那部分我想你已经明白了。

现在,当您将 cubenum 更改为不返回任何内容时,您的完整程序是

#include <stdio.h>

int main(void)
{
    printf("Answer is: %f", cubenum(3.0));
    return 0;
}

void cubenum(double number)
{
    double result = number * number * number;
    printf("Answer is: %f", result);
}

隐式函数声明仍然是int cubenum(),函数定义的原型是void cubenum(double)。作为那些非常老的 C 程序的另一个兼容性特性,这些被认为是不是冲突的返回类型,并且编译器接受该程序。这是因为 void 类型是在 1989 年的 C 标准中发明的。在此之前编写的程序将改为给 cubenum 没有返回类型...

cubenum(number)
    double number;
{
    double result = number * number * number;
    printf("Answer is: %f", result);
}

... 从技术上讲,它声明它返回int!在 C89 之后,这些程序立即进行了更新,为它们的函数提供了 void 类型的无返回值,但同时停止依赖隐式声明的工作量太大,因此编译器产生了一个特殊情况,其中 @ 987654349@ 和 void foo() 被认为不冲突。

(顺便说一句,由于另一个向后兼容性考虑——“旧式函数定义”,你可以在上面的代码片段中看到——C 中的首选风格是将函数定义的左花括号放在自己的位置行,即使所有其他左大括号都被“拥抱”。)

最后,当您void cubenum(); 放在main 之上时,编译器才会正式意识到cubenum 什么也不返回。当它知道这一点时,它就知道printf("%f", cubenum(3.0)); 不正确,因为它使用了cubenum 不存在的返回值,并且它出于那个 的原因拒绝了该程序。


您不应该在新程序中依赖任何这些向后兼容功能。我看到您使用的是 GCC,因此请将您的编译选项设置为:

-std=gnu11 -g -Og -Wall -Wpedantic -Wstrict-prototypes -Wold-style-definition -Werror

这将禁用几乎所有的向后兼容性功能。 (还有很多 warning options,您可能需要考虑打开更多。-Wwrite-strings-Wextra 对于新代码 IMNSHO 特别有用。)(不要使用超一致性模式,@987654359 @,直到您对自己正在做的事情有更多了解;它会破坏系统标头,并且会启用您几乎肯定不想要的“三字形”错误功能。)

【讨论】:

    【解决方案2】:

    而如果上面的 cubenum 定义被替换为以下 没有返回的定义那么它不会产生任何错误 未声明 cubenum 原型:

    因为当您没有显式声明函数时,一些 C 编译器会隐式声明。通常,它为返回类型和参数提供int

    一个足够好的编译器应该会告诉你隐式声明。

    这很危险,因为编译器不知道函数的确切原型。所以你可能会得到一个非常不可预测的结果。

    当原型被声明为 void cubenum();上面的立方数 没有返回的定义会产生以下错误:

    这是另一个问题,因为您尝试将printf 的返回值设为void 函数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-06
      相关资源
      最近更新 更多