预编译 headers 是如何工作的

How precompiled headers actually works

我所有的问题都与 vc++ 编译器有关,但我猜其他 c++ 编译器也有相同的行为。

  1. 预编译的 headers 是 preprocessor-related 的东西还是这就是编译过程?或两者?我有几个猜测:
    • PCH-engine只是扩展了MACRO-definitions和嵌套了headers并将它们翻译成二进制格式(pch文件)。在这种情况下,所有 source-files (我的意思是 cpp/hpp 也可能包含在 PCH 中)将在项目的每个源文件中重新编译。还是不行?
    • 全部source-files会只编译一次拉成单个obj-file?比如这个例子中会编译多少次变体库? IE。只有一次 - 在 PCH 中或两次 - 不在 PCH 中但在两个 *.cpp 文件中或三次 - 在 PCH 和两个 *.cpp 文件中?为什么?

//stdafx.h
#include <boost/variant/variant.hpp>

//test1.cpp
#include "stdafx.h"
#include <boost/variant/variant.hpp>
...

//test2.cpp
#include "stdafx.h"
...
  1. 我应该将哪些文件放入预编译headers?我想这是在项目中到处使用并且很少改变的东西。图书馆呢,例如 boost?我们只在少数source-files中使用boost,我们应该把它放在PCH中吗?

我对 VC++ 的内部结构没有特别的了解。然而,如果对编译器设计和理论有所了解,这些 so-called "precompiled headers" 只不过是经典编译器设计的初始词法分析和标记化阶段的结果。

考虑一个包含以下内容的简单 header 文件:

#ifdef FOO
#define BAR 10
#else
#undef FOOBAR
class Foo {
public:
     void bar();
};
#include "foobar.h"
#endif

您必须了解使用 so-called "pre-compiled" header 的效果必须与使用 header 文件相同。

在这里,你真的不知道这个 header 文件要做什么。这完全取决于实际包含 header 文件时定义的预处理器宏。您不知道此 header 文件将定义哪些宏。您不知道此 header 文件将取消定义哪些宏。您不知道此 header 文件将包含哪些其他 header 文件。在这里你真的不知道很多。

对于 "precompile" 一个 header 文件,您在概念上唯一可以做的就是 pre-parse 它。将语言的各个元素、各个关键字(如“#ifdef”、"class" 和所有其他关键字)转换为各个二进制标记。删除任何注释、空格等...

编译传统语言的第一阶段涉及将纯文本源解析为内部语言元素。词法分析和标记化阶段。在解析了各个语言元素之后,将尝试弄清楚解析后的源代码应该如何变成 object 模块。这就是编译器 99% 的工作所在。最初的词法分析阶段并不多,但是这几乎是您可以对 "precompile" 源代码执行的所有操作,并保存标记化源的内部二进制表示,以便在实际使用时可以跳过此阶段使用 "precompiled" 源代码的代码已编译。

我假设 VC++ 对预编译的 headers 的内容几乎没有限制,如果根本没有限制的话。但是,如果有一些限制——比如,预编译的 headers 不能有任何条件预处理器指令 (ifdef/ifndef) 除了经典的守卫——那么就可以做更多的工作来产生预编译的 headers,并在此处节省更多工作。对预编译 header 内容的其他限制也可能导致一些附加功能被转移到预编译阶段。

由于stdafx.cpp编译的预编译header文件是stdafx.h。开发人员会在这个 header 中放置很少更改且经常需要的 header 文件和符号。比如Windows.hvector以及一些全局的宏和符号。经常使用,我指的是给定项目中的所有文件。

此类文件 (PCH) 的用途和用途是什么?那么,VC++ 编译器将编译整个 stdafx.h 文件,递归地,所有 header 都包含所有宏和其他符号。第一次,会花很多时间,会产生一个PCH文件(因此pre-compiled h标题)。在后续构建中,通过 stdafx.h 包含的元素将不会被重新编译(因为它们已经采用某种 binary/pre-compiled 格式)。这减少了构建时间,并且它会根据通过 stdafx.h 文件放置的元素(headers、符号等)的数量而有所不同。

如果您的代码库很大,而 stdafx 中的元素较少,您将不会获得优势(例如,到处都包含通用 Windows 和 STL header,到处都有 externs 和 typedef) .最好找到这些元素,将它们放入 stdafx.h,然后从 header/CPP 文件中删除它们。这将大大减少整体构建时间。

您可以在此处进行更改:

我认为 MSVC 寻找 <application_name>.pch 作为翻译单元的唯一预编译 header,并使用它代替包含在 #line 1 "c:\application_name\stdafx.h" 下的嵌入 header预处理 .i 文件(如果可用)。预编译的 header 可能是序列化的 AST,即 header 已被词法分析并解析为 AST 表示。然后它不需要对预处理输出的这个区域进行 lex(或解析),只使用 .pch,它包含在 stdafx.h 下预处理器输出中写入的内容的 lex+parse 输出。预处理器已经在 stafx.h 上完成了所有其他工作,例如扩展宏(不会出现在 .i 文件/预处理器输出中)。