23

在阅读这个问题的一些答案时,我开始想知道为什么编译器在第一次遇到函数时确实需要知道它。在解析收集其中声明的所有符号的编译单元时添加一个额外的通道不是很简单,以便声明和使用它们的顺序不再重要吗?

有人可能会争辩说,在使用函数之前声明它们肯定是一种很好的风格,但我想知道,在 C++ 中这是强制性的还有其他原因吗?

编辑 - 一个示例来说明:假设您必须在头文件中内联定义函数。这两个函数相互调用(可能是递归树遍历,树的奇数层和偶数层的处理方式不同)。解决此问题的唯一方法是在另一个函数之前对其中一个函数进行前向声明。

一个更常见的示例(尽管是类,而不是函数)是具有private构造函数和工厂的类的情况。工厂需要知道类才能创建它的实例,而类需要知道工厂才能进行friend声明。

如果这是过去的要求,为什么在某个时候没有删除它?它不会破坏现有的代码,不是吗?

4

11 回答 11

12

您建议如何解决在不同翻译单元中定义的未声明标识符?

C++ 没有模块概念,但作为从 C 的继承,具有单独的翻译。C++ 编译器将自行编译每个翻译单元,根本不了解其他翻译单元。(除了export打破这个,这可能是它,遗憾的是,它从未起飞的原因。)
头文件,这是你通常放置在其他翻译单元中定义的标识符的声明的地方,实际上只是一种非常笨拙的滑动方式相同声明到不同的翻译单元。它们不会让编译器知道还有其他翻译单元,其中定义了标识符。

编辑您的其他示例:
由于包含所有文本而不是正确的模块概念,编译对于 C++ 来说已经花费了非常长的时间,因此需要另一个编译过程(其中编译已经分为几个过程,并非所有过程都可以优化和合并, IIRC) 会恶化一个已经很糟糕的问题。在某些情况下,改变它可能会改变重载分辨率,从而破坏现有代码。

请注意,C++ 确实需要额外的传递来解析类定义,因为在类定义中内联定义的成员函数会被解析,就好像它们是在类定义后面定义的一样。但是,这是在考虑 C with Classes 时决定的,因此没有现有的代码库可以破坏。

于 2011-01-21T10:41:21.923 回答
10

从历史上看,C89 允许您这样做。当编译器第一次看到一个函数的使用并且它没有预定义的原型时,它“创建”了一个与函数的使用相匹配的原型。

当 C++ 决定向编译器添加严格的类型检查时,它决定现在需要原型。此外,C++ 继承了 C 的单遍编译,因此它无法添加第二遍来解析所有符号。

于 2011-01-21T15:24:37.167 回答
9

因为 C 和 C++ 是语言。早期的编译器没有很多内存,所以这些语言被设计成编译器可以从上到下读取文件,而不必考虑整个文件

于 2011-01-21T10:41:02.997 回答
4

我想到两个原因:

  • 它使解析变得容易。不需要额外的通行证。
  • 它还定义了范围;符号/名称仅在其声明后可用。 意思是,如果我声明了一个全局变量,那么这一行之后的代码可以使用它,但不能使用该行之前的代码!全局函数的参数相同。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!
}

输出:

无效 g(双)
无效 g(int)

请参阅 ideone 的输出:http ://www.ideone.com/EsK4A

于 2011-01-21T10:34:32.623 回答
2

主要原因将是使编译过程尽可能高效。如果您添加额外的通行证,您将同时增加时间和存储空间。请记住,C++ 是在四核处理器时代之前开发的 :)

于 2011-01-21T10:33:46.470 回答
2

C 编程语言的设计使编译器可以实现为一次性编译器。在这样的编译器中,每个编译阶段只执行一次。在这样的编译器中,您不能引用稍后在源文件中定义的实体。

此外,在 C 中,编译器一次只解释一个编译单元(通常是一个 .c 文件和所有包含的 .h 文件)。因此,您需要一种机制来引用另一个编译单元中定义的函数。

允许一次性编译器并能够将项目拆分为小型编译单元的决定是因为当时可用的内存和处理能力非常紧张。并且允许前向声明可以通过单个功能轻松解决问题。

C++ 语言是从 C 派生的,并继承了它的特性(因为它希望尽可能与 C 兼容以简化转换)。

于 2011-01-21T10:47:26.320 回答
1

我猜是因为 C 已经很老了,当时 C 的设计效率很高,因为 CPU 的速度要慢得多。

于 2011-01-21T10:37:41.923 回答
1

由于 C++ 是一种静态语言,编译器需要检查值的类型是否与函数参数中预期的类型兼容。当然,如果你不知道函数签名,你就不能做这种检查,从而违背了静态编译器的目的。但是,由于您在 C++ 中拥有银牌,我想您已经知道这一点。

C++ 语言规范是正确的,因为当硬件不如今天可用的那么快时,设计人员不想强制使用多通道编译器。最后,我认为,如果 C++ 是今天设计的,那么这种强制就会消失,但是到那时,我们就会有另一种语言 :-)。

于 2011-01-21T10:39:21.740 回答
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.cand test0.c):

gcc -std=c89 -pedantic-errors test.c test0.c -o test

为什么它会打印一些奇怪的东西(至少在 linux-x86 和 linux-amd64 上)?你能一眼看出代码中的问题吗?现在尝试在命令行中替换c89c99- 编译器会立即通知您您的错误。

与 C++ 相同。但是在 C++ 中,实际上还有其他需要函数声明的重要原因,它们在其他答案中进行了讨论。

* 但有未定义的行为

于 2016-11-22T14:40:54.760 回答
0

尽管如此,您有时可以在声明函数之前使用它(严格地说:“之前”是关于读取程序源的顺序)——在一个类中!:

class A {
public:
  static void foo(void) {
    bar();
  }
private:
  static void bar(void) {
    return;
  }
};

int main() {
  A::foo();
  return 0;
}

(根据我的测试,将类更改为命名空间不起作用。)

这可能是因为编译器实际上在类声明之后将成员函数定义放在类内部,正如有人在答案中指出的那样。

相同的方法可以应用于整个源文件:首先,删除除声明之外的所有内容,然后处理推迟的所有内容。(要么是一个两遍编译器,要么是足够大的内存来保存推迟的源代码。)

哈哈!因此,他们认为整个源文件太大而无法保存在内存中,但具有函数定义的单个类不会:他们可以允许整个类位于内存中并等待声明被过滤掉(或者为类的源代码做第二遍)!

于 2011-01-29T04:11:50.423 回答
-1

我记得在 Unix 和 Linux 中,你有GlobalLocal. 在您自己的环境中,本地适用于函数,但不适用于Global(system). 您必须声明函数Global

于 2017-01-27T06:39:02.667 回答