与c#和Java相比,编译c++文件需要很长时间。编译一个c++文件比运行一个正常大小的Python脚本花费的时间要长得多。我目前使用vc++,但它与任何编译器是一样的。为什么会这样?

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


当前回答

你得到的代价是程序运行得稍微快一点。在开发期间,这对您来说可能是一种冷漠的安慰,但一旦开发完成,并且程序只是由用户运行时,它就会变得非常重要。

其他回答

编译语言总是比解释语言需要更大的初始开销。此外,也许您没有很好地组织您的c++代码。例如:

#include "BigClass.h"

class SmallClass
{
   BigClass m_bigClass;
}

编译速度比:

class BigClass;

class SmallClass
{
   BigClass* m_bigClass;
}

另一个原因是使用C预处理器来定位声明。即使使用了头保护,.h仍然必须在每次包含它们时被反复解析。一些编译器支持预编译的头文件,可以帮助解决这个问题,但它们并不总是被使用。

参见:c++常见问题答案

c++被编译成机器代码。所以你有预处理器,编译器,优化器,最后是汇编器,所有这些都必须运行。

Java和c#被编译成字节码/IL, Java虚拟机/。NET框架执行(或JIT编译成机器代码)之前执行。

Python是一种解释型语言,它也被编译成字节码。

我相信还有其他原因,但总的来说,不需要编译为本机机器语言可以节省时间。

构建C/ c++:到底发生了什么,为什么要花这么长时间

相当大一部分软件开发时间不是花在编写、运行、调试甚至设计代码上,而是花在等待代码完成编译上。 为了让事情变得更快,我们首先必须理解编译C/ c++软件时发生了什么。步骤大致如下:

配置 构建工具启动 依赖项检查 编译 链接

现在,我们将更详细地查看每个步骤,重点关注如何使它们更快。

配置

这是开始构建的第一步。通常意味着运行配置脚本或CMake、Gyp、SCons或其他工具。对于非常大的基于autotools的配置脚本,这可能需要一秒钟到几分钟的时间。

这一步很少发生。它只需要在更改配置或更改构建配置时运行。如果不改变构建系统,就没有多少事情可以加快这一步。

构建工具启动

这是在IDE上运行make或单击构建图标(通常是make的别名)时发生的情况。构建工具二进制文件启动并读取其配置文件以及构建配置,这通常是同一件事。

根据构建的复杂性和大小,这可能需要几秒到几秒的时间。这本身并没有那么糟糕。不幸的是,大多数基于make的构建系统在每次构建时都会调用几十到几百次make。这通常是由递归使用make(这是不好的)引起的。

应该注意的是,Make如此缓慢的原因并不是实现错误。Makefiles的语法有一些怪癖,使得真正快速的实现几乎不可能。当与下一步结合使用时,这个问题会更加明显。

依赖项检查

Once the build tool has read its configuration, it has to determine what files have changed and which ones need to be recompiled. The configuration files contain a directed acyclic graph describing the build dependencies. This graph is usually built during the configure step. Build tool startup time and the dependency scanner are run on every single build. Their combined runtime determines the lower bound on the edit-compile-debug cycle. For small projects this time is usually a few seconds or so. This is tolerable. There are alternatives to Make. The fastest of them is Ninja, which was built by Google engineers for Chromium. If you are using CMake or Gyp to build, just switch to their Ninja backends. You don’t have to change anything in the build files themselves, just enjoy the speed boost. Ninja is not packaged on most distributions, though, so you might have to install it yourself.

编译

此时,我们最终调用编译器。省事起见,以下是大致采取的步骤。

合并包括 解析代码 代码生成和优化

与流行的观点相反,编译c++实际上并没有那么慢。STL很慢,大多数用于编译c++的构建工具都很慢。然而,有更快的工具和方法来减轻语言中缓慢的部分。

使用它们需要一些体力,但好处是不可否认的。更快的构建时间会让开发人员更快乐,更敏捷,并最终产生更好的代码。

在大型面向对象项目中,重要的原因是c++很难限制依赖关系。

私有函数需要在它们各自的类的public头文件中列出,这使得依赖关系比它们需要的更具传递性(传染性):

// Ugly private dependencies
#include <map>
#include <list>
#include <chrono>
#include <stdio.h>
#include <Internal/SecretArea.h>
#include <ThirdParty/GodObjectFactory.h>

class ICantHelpButShowMyPrivatePartsSorry
{
public:
    int facade(int);

private:
    std::map<int, int> implementation_detail_1(std::list<int>);
    std::chrono::years implementation_detail_2(FILE*);
    Intern::SecretArea implementation_detail_3(const GodObjectFactory&);
};

如果在头文件的依赖树中重复使用这种模式,就会产生一些间接包含项目中大部分头文件的“神头文件”。它们就像上帝对象一样无所不知,只是在绘制它们的包含树之前,这一点并不明显。

这会以两种方式增加编译时间:

它们添加到包含它们的每个编译单元(.cpp文件)的代码量很容易比cpp文件本身多很多倍。从这个角度来看,catch2.hpp是18000行,而大多数人(甚至是ide)开始难以编辑超过1000-10000行的文件。 编辑头文件时必须重新编译的文件数量不包含在依赖它的真实文件集中。

是的,有一些缓解措施,比如前向声明,它有缺点,或者pimpl习惯用法,它是非零成本抽象。尽管c++在你能做的事情上是无限的,但如果你偏离了它的本意,你的同事会想知道你到底在吸什么。

最糟糕的是:如果你仔细想想,在它们的公共头中声明私有函数的需求甚至是不必要的:成员函数的道德等效可以在C中被模仿,而且通常也被模仿,这不会重现这个问题。