在阅读this question的一些答案时,我开始想知道为什么 的编译器在第一次遇到它时需要知道一个函数。在解析收集其中声明的所有符号的编译单元时添加额外的通道是不是很简单,这样它们的声明和使用顺序就不再重要了?
有人可能会争辩说,在使用之前声明函数肯定是好的风格,但我想知道,在C ++中这是强制性的还有其他原因吗?
编辑 - 举例说明:假设您必须使用在头文件中内联定义的函数。这两个函数相互调用(可能是递归树遍历,其中树的奇数层和偶数层的处理方式不同)。解决这个问题的唯一方法是在另一个函数之前声明其中一个函数。
一个更常见的例子(虽然有类,而不是函数)是具有private
构造函数和工厂的类的情况。工厂需要知道该类才能创建它的实例,并且该类需要知道friend
声明的工厂。
如果要求来自以前的日子,为什么在某些时候没有删除?它不会破坏现有代码,是吗?
答案 0 :(得分:12)
您如何建议解析 未声明的 标识符 在其他翻译单元中定义 ?
C ++没有模块概念,但是有单独的翻译作为C的继承.C ++编译器将自己编译每个翻译单元,根本不了解其他翻译单元。 (除了export
打破了这个,这可能是为什么它可悲的是,从未起飞。)
头文件 ,这是您通常放置在其他翻译单元中定义的标识符声明的地方,实际上只是将相同的声明滑入不同的翻译单元的一种非常笨拙的方式。它们不会使编译器意识到存在其中具有标识符的其他转换单元。
修改 了解您的其他示例:
由于所有文本包含而不是适当的模块概念,编译已经花费了很长时间C ++,因此需要另一个编译传递(其中编译已经被分成几个传递,并非所有这些都可以被优化和合并,IIRC)会恶化已经很糟糕了。改变这种情况可能会在某些情况下改变重载决策,从而破坏现有代码。
请注意,C ++确实需要额外的传递来解析类定义,因为类定义中内联定义的成员函数被解析为好像它们是在后面类定义中定义的。但是,这是在考虑C with Classes时决定的,因此没有现成的代码库可以打破。
答案 1 :(得分:9)
因为C和C ++是旧的语言。早期的编译器没有很多内存,因此这些语言的设计使编译器可以从上到下读取文件,而不必将文件视为一个整体。
答案 2 :(得分:9)
历史上C89允许你这样做。编译器第一次看到函数的使用并且它没有预定义的原型时,它“创建”了一个与函数的使用相匹配的原型。
当C ++决定在编译器中添加严格的类型检查时,就决定现在需要原型。此外,C ++继承了C的单遍编译,因此无法添加第二遍来解析所有符号。
答案 3 :(得分:3)
我想到两个原因:
int g_count;
,这行后面的代码可以使用它,但不能使用行之前的代码!全局函数的相同论点。例如,请考虑以下代码:
void g(double)
{
cout << "void g(double)" << endl;
}
void f()
{
g(int());//this calls g(double) - because that is what is visible here
}
void g(int)
{
cout << "void g(int)" << endl;
}
int main()
{
f();
g(int());//calls g(int) - because that is what is the best match!
}
输出:
void g(double)
void g(int)
请参阅ideone的输出:http://www.ideone.com/EsK4A
答案 4 :(得分:2)
主要原因是使编译过程尽可能高效。如果您添加额外的通行证,则需要添加时间和存储空间。请记住,C ++是在四核处理器之前开发的:)
答案 5 :(得分:2)
设计了C编程语言,以便编译器可以实现为one-pass compiler。在这样的编译器中,每个编译阶段仅执行一次。在这样的编译器中,您不能引用稍后在源文件中定义的实体。
此外,在C中,编译器一次只能解释单个编译单元(通常是.c文件和所有包含的.h文件)。因此,您需要一种机制来引用另一个编译单元中定义的函数。
决定允许一次通过编译器并能够在小型编译单元中拆分项目,因为当时内存和处理能力非常紧张。允许前向声明可以通过单一功能轻松解决问题。
C ++语言源自C并从中继承了该功能(因为它希望尽可能与C兼容以简化转换)。
答案 6 :(得分:1)
我想因为C很老,而且在设计C时,编译效率很高,因为CPU速度要慢得多。
答案 7 :(得分:1)
由于C ++是一种静态语言,编译器需要检查值的类型是否与函数参数中预期的类型兼容。当然,如果你不知道函数签名,就不能做这种检查,从而违背了静态编译器的目的。但是,既然你有一个C ++的银徽章,我想你已经知道了。
C ++语言规范是正确的,因为当硬件没有现在的速度快时,设计人员不想强制使用多遍编译器。最后,我认为,如果C ++是今天设计的,那么这个拼版就会消失但是接下来,我们会有另一种语言: - )。
答案 8 :(得分:1)
即使在C99中强制执行此操作的最大原因之一(与C89相比,您可能具有隐式声明的函数)是隐式声明非常容易出错。请考虑以下代码:
第一档:
#include <stdio.h>
void doSomething(double x, double y)
{
printf("%g %g\n",x,y);
}
第二档:
int main()
{
doSomething(12345,67890);
return 0;
}
这个程序是一个语法上有效的* C89程序。您可以使用此命令使用GCC编译它(假设源文件名为test.c
和test0.c
):
gcc -std=c89 -pedantic-errors test.c test0.c -o test
为什么打印出一些奇怪的东西(至少在linux-x86和linux-amd64上)?你能一目了然地看到代码中的问题吗?现在尝试在命令行中用c89
替换c99
- 然后编译器会立即通知您错误。
与C ++相同。但是在C ++中,实际上需要函数声明的其他重要原因还有其他答案中讨论的。
*但有未定义的行为
答案 9 :(得分:0)
尽管如此,你可以在有时声明函数之前使用函数(严格的措辞:“之前”是关于读取程序源的顺序) - 在类中!:
class A {
public:
static void foo(void) {
bar();
}
private:
static void bar(void) {
return;
}
};
int main() {
A::foo();
return 0;
}
(根据我的测试,将类更改为命名空间不起作用。)
这可能是因为编译器实际上在类声明之后将成员函数定义放在类中,正如有人在答案中指出的那样。
同样的方法可以应用于整个源文件:首先,删除除声明之外的所有内容,然后处理推迟的所有内容。 (要么是两遍编译器,要么是足够大的内存来保存推迟的源代码。)
哈哈!所以,他们认为整个源文件太大要保留在内存中但是单个具有函数定义的类不会:它们可以允许整个类坐在内存中等待声明被过滤掉(或者为类的源代码做第二遍)!
答案 10 :(得分:0)
我记得在Unix和Linux上,你有Global
和Local
。在您自己的环境中,本地适用于函数,但不适用于Global(system)
。您必须声明函数Global
。