预编译头

预编译头(precompiled header)是程序设计时把头文件编译为中间格式(如目标文件),以节约在开发过程中编译器反复编译该头文件的开销。 C语言C++语言Objective C语言等都有类似的技术。

有的头文件包含了巨量的源代码(如著名的windows.h),或者使用模板编程时要生成巨大的头文件模板库(如Eigen math library英语List of numerical librariesBoost C++ libraries英语Boost C++ libraries)。为减少编译时间,某些编译器允许把头文件编译为某种中间形式称为预编译头(precompiled header),后续再编译源文件时就可以尽量直接使用这些预编译头。

简单示例

C++文件source.cpp包括header.hpp

//header.hpp
...
//source.cpp
#include "header.hpp"
...

首次编译source.cpp时,编译器生成header.pch的预编译头。以后再编译该程序时,编译器会比较该头文件的时间戳,如果头文件没有改变,编译器直接使用预编译头。

Visual Studio

Microsoft Visual Studio采取一个头文件(默认命名为stdafx.h),其内容是整个软件项目中被各个编译单元所使用的源代码,并极少修改。每个编译单元的源代码从最开始之处直至#include "stdafx.h"的内容完全相同。其中一个编译单元(源文件默认命名为"stdafx.cpp")以#include "stdafx.h"为最后一行(不考虑程序注释),并用编译选项/Yc"stdafx.h"编译,这将生成预编译头(默认命名为"<项目名>.pch"),包含了到#include "stdafx.h"这一行为止的所有代码编译结果。

项目中的其他编译单元,使用/Yu"stdafx.h"编译,这将把从开头直至#include "stdafx.h"这一行的源代码跳过编译过程,直接用预编译头(默认命名为"<项目名>.pch")代替。

stdafx中的AFX代表Application Framework eXtensions。AFX是Microsoft Foundation Classes (MFC)的旧称。编程者也可以用其他名字的头文件代替stdafx.h。

Visual Studio编译器确定预编译头的文件名,依照下述次序:[1]

  1. 编译器选项/Fp中给出的参数;
  2. 源文件中预处理器指令#pragma hdrstop [( "filename" )]给出的预编译头的文件名。源文件从开始之处至该行#pragma hdrstop的内容,用于创建预编译头(若使用/Yc选项)或者被预编译头取代(若使用/Yu选项)。
  3. /Yu"头文件基名.h"中的文件的base name加上后缀.PCH;
  4. 当前源文件的的base name加上后缀.PCH。

GCC

GCC3.4版开始支持预编译头。当编译器输入文件为头文件时(例如sample.h),产生预编译头,其文件名称是原来的头文件追加".gch"后缀(如sample.h.gch)。当gcc编译源文件时,对于遇到的每个头文件,都在INCLUDE路径中的每个目录下,先搜寻追加".gch"后缀的预编译头,如果没有再搜索该头文件。如果找到预编译头且满足一些条件,则直接使用该预编译头;否则就把该头文件在源文件中展开并编译之。[2] 可以在编译源文件时使用-H选项,如果显示的预编译头的全路径这一行是以"!"开始,说明编译器成功使用了预编译头;如果这一行以"x"开始,说明编译器找到了这一预编译头但未能使用它。

gcc对C语言的头文件与C++的头文件产生的格式不同,不能混用。如果头文件的名字不是".h"或".hpp"后缀结尾(C++标准库的头文件基本如此),可以用编译选项-x c++-header让gcc把当前的输入文件当作C++头文件来处理。用编译选项-x c-header让gcc把当前的输入文件当作C头文件来处理。[3]

使用预编译头时必须满足下列条件:

  • 编译一个源文件,至多只能使用一个预编译头。
  • 编译源文件时,如果先于预编译头对应的头文件编译器就扫描到C/C++的词(token),则不能使用该预编译头。也就是说,在预编译头对应的头文件之前的源文件中,只能有预处理指令(preprocessor directive)。 不能在另一个头文件中引入使用预编译头。
  • 预编译头与源文件必须是相同语言。不能在编译C++源文件时使用C语言的预编译头。
  • 预编译头与源文件应当由同一个编译器编译。以保证二进制兼容。
  • 在预编译头之前定义的宏(macro)或者被引入源程序时与预编译头生产时完全一致,或者预编译头没有用到该宏。这包括了由编译选项-D定义的宏、#define定义的宏、以及某些编译选项如-O或-Wdeprecated隐式定义的宏。
  • 源文件用-g选项编译以获取调试信息时,其使用的预编译头也应该是用-g编译选项生成的。但是,用-g选项编译生成的预编译头,也可用在没有调试信息输出的源文件编译。
  • 生成与使用预编译头,必须使用相同的-m选项。该编译选项给出目标硬件平台的相关信息。
  • 生成与使用预编译头,必须使用相同的-fexceptions选项。
  • 生成与使用预编译头,必须使用以-f, -p, -O开头的相同的命令行选项。目前还不知道哪些编译选项可以安全地改变或者决不能改变,最安全的选择是生成与使用预编译头使用相同的编译选项。已知下述选项可以安全地改变:
 -fmessage-length=  -fpreprocessed  -fsched-interblock -fsched-spec  
 -fsched-spec-load  -fsched-spec-load-dangerous 
 -fsched-verbose=number  -fschedule-insns  -fvisibility= 
 -pedantic-errors

上述条件如果不被满足,编译器将自动使用头文件而不是对应的预编译头。

C++Builder

在项目默认配置中,C++Builder编译器无保留地生成在#pragma hdrstop之前所有头文件的预编译头。如果可能的话,预编译头会被项目的所有模块共享。例如,当使用Visual Component Library时,通常首先会包含vcl.h头文件,该头文件含有大多数经常被用到的VCL头文件。因此,预编译头被全项目共享可以显著减少生成时间。

另外,C++Builder能设置为使用一个特定的头文件作为预编译头,和Visual C++提供的机制类似。

C++Builder 2009引入了“Precompiled Header Wizard(预编译头向导)”来为被包含的头文件提取所有源码,然后自动为特定的文件分类(例如排除那些不是项目成员或者没有Include防范的头文件)、生成并测试预编译头。

外部链接

参考文献

  1. ^ MSDN Help Document: hdrstop. [2015-06-07]. (原始内容存档于2018-09-24). 
  2. ^ GCC online documentation §3.20: Using Precompiled Headers. [2015-06-07]. (原始内容存档于2015-09-18). 
  3. ^ GCC online documentation §3.2 Options Controlling the Kind of Output. [2015-06-07]. (原始内容存档于2015-09-18).