为什么我们需要在C文件的源文件中包含原型?

时间:2017-11-26 22:38:56

标签: c include

我试图理解为什么我们需要在源代码中包含函数原型。根据我的理解: 为了从多个源文件获取可执行文件,需要将源文件转换为目标文件。对象文件可以相互引用而没有任何问题:例如,主文件可以调用将从另一个源文件编译的foo函数。 链接器负责解析所有源文件中对各种函数/符号的所有引用。

我成功地独立编译了这两个文件然后生成了可执行文件。你可以注意到没有#include" function.h"在main.c

function.c

int foo() {
return 1;
}

的main.c

int main() {
return foo();
}

使用的命令:

gcc mainc.c function.c -S
gcc main.o function.o -o exec

由于函数foo的隐式声明,我收到第一个命令的警告,但是输出exec正在工作。所以我的问题是:

为什么我们需要包含函数原型?

4 个答案:

答案 0 :(得分:3)

当没有原型时,你的例子与C看到函数的方式相符。

在C89及更早版本中,它允许调用一个尚未声明的函数,在这种情况下,编译器假定为int f(),因此它在您的示例中有效(并且链接器找到函数名称所以没有抱怨在那一边)

但是如果将返回值更改为float或添加参数,编译器将生成错误的参数传递/返回值分配代码,并且您将得到奇怪的结果。

(一个很好的例子就是在没有包含double No math.h include - sqrt function return value?)的情况下尝试调用没有原型的floatmath.h的数学函数。

这就是原型在这里的原因。指导编译器如何正确调用外部函数。

请注意gcc警告即使没有任何警告标志(因此,在最低警告级别,从来不是您的代码的良好信号):

test.c:11:12: warning: implicit declaration of function 'foo' [-Wimplicit-function-declaration]
     return(foo());
            ^~~

答案 1 :(得分:0)

我们认为编译器并不聪明,有时我们需要告诉编译器函数需要什么:例如参数,输出,返回类型...... 当您没有对函数进行原型设计时,编译器会扣除您的函数所期望但不准确的内容。 当你对函数进行原型设计时,你只需告诉编译器你可能有一个名为" foo"的函数。在您的程序中采用void参数

很抱歉,如果我犯了语法错误,但英语不是我的母语。 此致

答案 2 :(得分:0)

是的,当范围内没有函数原型时,可以调用函数。事实上,这就是C多年来的工作方式。所以从这个意义上说,不需要函数原型。

但是,在缺少函数原型的情况下,正确调用外部函数 会给程序员带来沉重的负担,使得所有外部函数调用完全正确。如果使用错误的参数调用函数,或者函数返回类型不匹配。你得到奇怪的错误结果,编译器通常无法警告你。 (曾几何时,lint可能会警告你,但是lint已经逐渐消失了。)

函数原型是一种工具,可以帮助程序员(和编译器)更可靠地匹配外部函数调用中的参数和返回类型。随着时间的推移,他们已经从不存在(早期的C根本没有它们),到可选的,到需要的。

现在需要它们,因为标准说它们是。为什么标准要求它们?它并不是因为它们是编译器生成适当代码所必需的。 (同样,历史表明,早期的C编译器在没有它们的情况下完好无损。)相反,标准要求它们,因为今天的共识是,获得始终可靠的程序比让C程序员成为牛仔并尝试做更重要一切都在。

答案 3 :(得分:0)

正如我在评论中提到的,从C99开始,不再支持隐式int声明 - 所有函数必须在使用前显式声明

部分原因是确保调用者和函数之间的二进制兼容性。如果调用者希望函数返回32位int,但函数被定义为返回64位double,那么您将遇到某种类型的运行时问题(或者该值将被截断,或内存将被覆盖,或其他一些问题)。

如果调用者和函数是在单独的源文件中定义的,那么除非您在调用者中声明了该函数,否则编译器无法知道它们是否兼容。如果它们在同一源文件中定义,则编译器可以将隐式函数声明与定义进行比较,并在它们不匹配时发出诊断。

与更现代的语言相比,C&C的翻译模型相当原始,它依赖于您(程序员)为其提供确保最终产品有效所需的所有信息。这意味着包括外部函数的任何声明。

在80年代和90年代早期,荒谬很容易编写没有警告或错误编译的代码,但却有数十个定时炸弹潜伏,因为函数声明和定义没有& #39; t排队。执行显式函数声明是几十年的痛苦经历的结果。