为什么C++编译要花这么长时间?

为什么C++编译要花这么长时间?

几个原因

头文件

每一个编译单元都需要数百甚至数千个标头来(1)加载和(2)编译。它们中的每一个通常都必须为每个编译单元重新编译,因为预处理器确保在每个编译单元之间编译头的结果可能会有所不同。(宏可以在一个编译单元中定义,它改变报头的内容)。

这可能是主要原因,因为它需要为每个编译单元编译大量代码,此外,每个头文件都必须编译多次(每个包含它的编译单元编译一次)。

链接

编译完成后,所有的目标文件都必须链接在一起。这基本上是一个整体的过程,不能很好地并行化,并且必须处理整个项目。

解析

语法非常复杂,很难解析,严重依赖于上下文,而且很难消除歧义。这需要很长时间。

模板

在C#中,无论您的程序中有多少个List的实例化,List都是唯一编译的类型。在C++中,vector是一个完全独立于vector的类型,每个类型都必须单独编译。

除此之外,模板组成了一个完整的图灵完整的“子语言”,编译器必须解释它,这可能会变得非常复杂。即使是相对简单的模板元编程代码也可以定义递归模板,以创建数十个模板实例化。模板也可能导致极其复杂的类型,名称长得离谱,给链接器增加了很多额外的工作。(它必须比较许多符号名称,如果这些名称可以增长到数千个字符,这可能会变得相当昂贵)。

当然,它们加剧了头文件的问题,因为模板通常必须在头文件中定义,这意味着必须为每个编译单元解析和编译更多的代码。在纯C代码中,标头通常只包含前向声明,但实际代码很少。在C++中,几乎所有代码都驻留在头文件中的情况并不少见。

优化

C++允许进行一些非常戏剧性的优化。C#或Java不允许完全消除类(它们必须存在以进行反射),但即使是一个简单的C++模板元程序也可以很容易地生成数十或数百个类,所有这些类都会在优化阶段被内联并再次消除。

此外,C++程序必须由编译器完全优化。C#程序可以依赖JIT编译器在加载时执行额外的优化,C++不会得到任何这样的“第二次机会”。编译器生成的内容是经过优化的,就像它将要得到的一样。

机器

C++被编译成机器码,这可能比使用的字节码Java或.NET更复杂(特别是在x86的情况下)。(这只是出于完整性的考虑,因为它在评论等中提到了。在实践中,这一步不太可能超过总编译时间的一小部分)。

结论

这些因素中的大多数都是由C代码共享的,实际上C代码的编译效率相当高。在C++中,解析步骤要复杂得多,可能会占用更多的时间,但主要的违规者可能是模板。它们很有用,并使C++成为一种更强大的语言,但它们也在编译速度方面付出了代价。

相关推荐