预编译头
没有或很少条目链入本条目。 (2015年6月18日) |
预编译头(precompiled header)是程序设计时把头文件编译为中间格式(如目标文件),以节约在开发过程中编译器反复编译该头文件的开销。 C语言、C++语言、Objective C语言等都有类似的技术。
有的头文件包含了巨量的源代码(如著名的windows.h
),或者使用模板编程时要生成巨大的头文件模板库(如Eigen math library与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]
- 编译器选项/Fp中给出的参数;
- 源文件中预处理器指令
#pragma hdrstop [( "filename" )]
给出的预编译头的文件名。源文件从开始之处至该行#pragma hdrstop
的内容,用于创建预编译头(若使用/Yc
选项)或者被预编译头取代(若使用/Yu
选项)。 /Yu"头文件基名.h"
中的文件的base name加上后缀.PCH;- 当前源文件的的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防范的头文件)、生成并测试预编译头。
外部链接
参考文献
- ^ MSDN Help Document: hdrstop. [2015-06-07]. (原始内容存档于2018-09-24).
- ^ GCC online documentation §3.20: Using Precompiled Headers. [2015-06-07]. (原始内容存档于2015-09-18).
- ^ GCC online documentation §3.2 Options Controlling the Kind of Output. [2015-06-07]. (原始内容存档于2015-09-18).