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

与C#和Java相比,编译C ++文件需要很长时间。 编译C ++文件比运行普通大小的Python脚本要花费更长的时间。 我目前正在使用VC ++,但对于任何编译器都是一样的。 为什么是这样?

我能想到的两个原因是加载头文件和运行预处理器,但这似乎并不能解释为什么它需要这么长时间。


几个原因:

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

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

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

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

  • 模板:在C#中, List<T>是编译的唯一类型,无论您的程序中有多少个List实例。 在C ++中, vector<int>vector<float> vector<int>是一个完全独立的类型,每个都必须单独编译。

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

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

  • 优化: C ++允许进行一些非常戏剧性的优化。 C#或Java不允许完全消除类(它们必须出于反射的目的),但即使是一个简单的C ++模板元程序也可以轻松生成几十个或上百个类,所有这些类都在内核中被内联和再次消除相。

    而且,C ++程序必须由编译器完全优化。 AC#程序可以依赖JIT编译器在加载时执行额外的优化,C ++不会得到任何这样的“第二次机会”。 编译器产生的效果与它将要获得的优化一样。

  • 机器代码: C ++被编译为机器代码,这可能比字节代码Java或.NET的使用稍微复杂一些(特别是在x86的情况下)。
    (这仅仅是因为在评论和其他内容中提到了这一点,因此完整性被提及,实际上,这一步不可能只占编译时间的很小一部分。)

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


    任何编译器的放缓都不一定相同。

    我没有使用Delphi或Kylix,但回到MS-DOS时代,Turbo Pascal程序几乎可以立即编译,而等效的Turbo C ++程序只会抓取。

    两个主要区别是一个非常强大的模块系统和允许单次编译的语法。

    对于C ++编译器开发人员来说,编译速度当然不是优先考虑的事情,但C / C ++语法中也存在一些固有的复杂性,这使得它更难处理。 (我不是C方面的专家,但Walter Bright是,并且在构建各种商业C / C ++编译器之后,他创建了D语言。他的一个变化是强制使用上下文无关语法来使语言更易于解析。)

    另外,您会注意到,通常会设置Makefiles,以便每个文件都在C中分别编译,因此如果10个源文件全部使用相同的包含文件,那么包含文件将被处理10次。


    解析和代码生成实际上是相当快的。 真正的问题是打开和关闭文件。 请记住,即使使用包含警卫,编译器仍然打开.H文件,并读取每行(然后忽略它)。

    一位朋友曾经(在工作中感到无聊)采取了公司的应用程序,并将所有源代码和头文件都放入一个大文件中。 编译时间从3小时减少到7分钟。

    链接地址: http://www.djcxy.com/p/275.html

    上一篇: Why does C++ compilation take so long?

    下一篇: Why can't variables be declared in a switch statement?