在C ++中,在声明函数之前调用函数是一个编译器错误。但在C语言中,它可以编译。
#include<stdio.h>
int main()
{
foo(); // foo() is called before its declaration/definition
}
int foo()
{
printf("Hello");
return 0;
}
我已经尝试过并知道这是正确的,但我无法理解它背后的原因。任何人都可以解释编译过程是如何实际发生的,并且两种语言都有所不同。
答案 0 :(得分:12)
代码“编译”作为c程序这一事实并不意味着您可以这样做。编译器应警告函数foo()
的隐式声明。
在这种特殊情况下,隐式声明会声明一个相同的foo()
,并且不会发生任何不好的事情。
但假设以下情况,请说这是
<强>的main.c 强>
/* Don't include any header, why would you include it if you don't
need prototypes? */
int main(void)
{
printf("%d\n", foo()); // Use "%d" because the compiler will
// implicitly declare `foo()` as
//
// int foo()
//
// Using the "correct" specifier, would
// invoke undefined behavior "too".
return 0;
}
现在假设foo()
在另一个编译单元 1 foo.c 中定义为
<强> foo.c的强>
double foo()
{
return 3.5;
}
是否按预期工作?
你可以想象如果你使用malloc()
但不包括 stdio.h 会发生什么,这与我上面尝试解释的情况非常相似。
这样做会调用未定义的行为 2 ,因此术语“ Works ”在这种情况下不适用于可理解的意义。
这可以编译的原因是因为在过去很久以前它都是c标准所允许的,即c89标准。
c++标准从未允许这样做,所以如果你调用一个没有原型的函数(“声明”),你就无法编译c++程序代码在它被调用之前。
现代c编译器警告这一点,因为未定义行为可能很容易发生,并且因为忘记添加原型或包含适当的标题并不困难对于程序员来说,如果编译器可以对此进行警告而不是突然出现一个非常难以理解的错误,那么它会更好。
1 它不能在同一个文件中编译,因为它将使用不同的返回类型定义,因为它已经隐式声明 子>
2 从double
和int
是不同类型的事实开始,因此会有未定义的行为。
答案 1 :(得分:5)
开发C时,只需要能够调用它的函数名称。函数参数的匹配参数完全是程序员的业务;编译器并不关心你是否将三个浮点数传递给只需要一个整数的东西。
然而,事实证明这很容易出错,因此后来C语言的迭代添加了函数原型作为(仍然是可选的)附加限制。在C ++中,这些限制已经进一步加强:现在函数原型始终是强制性的。
我们可以推测出原因,但部分原因是因为在C ++中仅仅知道函数名称已经不够了。可以有多个具有相同名称但具有不同参数的函数,编译器必须确定要调用哪个函数。它还必须弄清楚如何调用(直接或虚拟?),甚至可能必须在模板函数的情况下生成代码。
鉴于我认为语言要求在调用函数时知道函数原型是有意义的。
答案 2 :(得分:2)
最初,C没有函数原型,C ++也不存在。
如果你说
extern double atof();
这说atof
是一个返回双重的函数。 (关于其论点没有任何说法。)
如果你那么说
double d = atof("1.3");
它会起作用。如果你说
double d2 = atof(); /* whoops, forgot the argument to atof() */
编译器不会抱怨,但如果你试图运行它会发生奇怪的事情。
在那些日子里,如果你想捕获与使用错误数量的参数调用函数相关的错误,那就是单独程序lint
的工作,而不是C编译器。
同样在那些日子里,如果你刚刚调用了编译器之前从未听说过的函数,就像这样:
int i = atoi("42");
编译器基本上假装你早先说过
extern int atoi();
这就是所谓的隐式函数声明。每当编译器看到对其名称不知道的函数的调用时,编译器就认为它是一个返回int的函数。
快进几年。 C ++发明了我们今天所知的函数原型。除此之外,它们还允许您声明函数所期望的参数的数量和类型,而不仅仅是它的返回类型。
快进几年,C采用功能原型,可选。如果你愿意,你可以使用它们,但是如果你不这样做,编译器仍会对它看到的任何未知函数调用进行隐式声明。
快进几个更多年,到C11。现在隐式int终于消失了。如果您在未声明函数的情况下调用函数,则需要编译器进行投诉。
但是即使在今天,你可能正在使用一个前C11编译器,它仍然对隐式int感到满意。如果您在调用函数之前忘记声明函数,则C11投诉编译器可能只发出警告(而不是编译查询错误)。并且符合C11的编译器可以提供关闭这些警告的选项,并静静地接受隐式int。 (例如,当我使用非常现代的clang时,我安排用-Wno-implicit-int
调用它,这意味着我不想要关于隐式int的警告,因为我有很多很多旧的我不想改写的代码。)
答案 3 :(得分:1)
为什么我可以在不声明的情况下调用C中的函数?
因为在C中,而不是在C ++中,没有原型的函数假定返回int
。
这是一个隐式声明该功能。如果该假设结果为真(函数稍后用返回类型int
声明),那么程序编译就好了。
如果该假设结果为假(假设返回int
,但实际上发现返回double
,那么你会得到一个编译器错误,即两个函数不能有相同的名字。 (例如,int foo()
和double foo()
不能同时存在于同一个程序中)
请注意,所有这些都是 C only 。
在C ++中,不允许隐式声明。即使它们是,但错误消息会有所不同,因为C ++具有函数重载。错误会说函数的重载只能通过返回类型而有所不同。 (重载发生在参数列表中,而不是返回类型)