我试图理解为什么我们需要在源代码中包含函数原型。根据我的理解: 为了从多个源文件获取可执行文件,需要将源文件转换为目标文件。对象文件可以相互引用而没有任何问题:例如,主文件可以调用将从另一个源文件编译的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正在工作。所以我的问题是:
为什么我们需要包含函数原型?
答案 0 :(得分:3)
当没有原型时,你的例子与C看到函数的方式相符。
在C89及更早版本中,它允许调用一个尚未声明的函数,在这种情况下,编译器假定为int f()
,因此它在您的示例中有效(并且链接器找到函数名称所以没有抱怨在那一边)
但是如果将返回值更改为float
或添加参数,编译器将生成错误的参数传递/返回值分配代码,并且您将得到奇怪的结果。
(一个很好的例子就是在没有包含double
No math.h include - sqrt function return value?)的情况下尝试调用没有原型的float
或math.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排队。执行显式函数声明是几十年的痛苦经历的结果。