http://apps.hi.baidu.com/share/detail/17562834
——谨以此文,悼念我等待MinGW编译时逝去的那些时间。
其 实刚开始编程的时候,我是丝毫不重视编译速度之类的问题的,原因很简单,因为那时我用BASICA。后来一直用到C++ Builder,尽管Borland的广告无时无刻不在吹嘘其编译速度,我却从没有对这个问题上心过,因为心里根本没有“编译速度慢”这种概念。没有坏, 哪来好?所谓矛盾的对立统一。遇到的第一个“慢”的编译器也许是javac,但因为Java的特殊性,也就容忍了。真正接触到世间的“恶势力”,还要算是 第一次使用GCC的时候……准确地说是MinGW。开源世界曾给我诸多惊喜,其一就是原来编译器也可以这么慢的。那时我不禁对开源社区肃然起敬,他们就用 这样的编译器,建立起了怎样一个多彩的世界!也在那时才明白了,Borland其实真的很了不起。
时至今日我也不是很了解Borland是怎么做到的,很久以来也不知道GCC是差在了哪里。然而……有一次心血来潮,忽然想看看 MinGW编译过程中加载的所有头文件。于是用了一下 -H 参数。结果是满意的,加载的头文件真多呀。接下来……开始感觉到另外的一些东西了。敢情,大部分编译时间是浪费在这里的呀?——“预编译头”的概念如鲸鱼 般跃出脑海。
预编译头技术是在VC中第一次了解的,其对编译速度的提高,绝对给人以深刻的印象。使用MinGW的时候居然忘了这个古老的咒语。是否正是我所需要的?百 度几下,结果令人失望,这方面的文献少得可怜,更令人沮丧的是还有不少人相信GCC是没有预编译头技术的。贼心不死的我打开 GCC官方文档,查找precompiled headers。慢着,居然如此顺利!——官方文档讨论篇幅并不长,但足以让我喊万岁了~不用多,一句话就够了,怎么说来着?Simply compile it!
所谓预编译头,就是把头文件事先编译成一种二进制的中间格式,供后续的编译 过程使用。不要把这个中间格式与. o/.obj/.a/.lib的格式混淆,他们是截然不同的,所以预编译头文件的特性和目标文件也不同(尽管他们都属于某种中间文件) 。——但也有类似的地方的,比如,它们都是编译器之间不兼容的^_^,就是说你不能把VC生成的预编译头拿到GCC上去用。甚至扩展名都不一样,VC的是 大家都熟悉的. pch,而GCC的,是.gch——今天的主角。
为什么要使用预编译头?再明确不过了,提高编译速度。为什么会提高编译速度?这么说吧,你有两个文件a.cpp和b.cpp,都包含了同一个头文件 c.h。那么正常的流程是:将c.h和a.cpp合并,编译成a.o;将c.h和b.cpp合并,编译成b.o;最后将a.o和b.o链接成可执行文件。 过程很简单,浪费时间之处也一目了然:头文件c.h的内容实际上被解析了两遍。也许你要说,当然要两遍了,因为头文件几乎是不生成任何代码的,只有依附于 具体的.cpp文件才有意义。正确,但那只是在代码执行过程中。但在代码编译的时候呢?编译器读入源代码,首先将其解析成为一种内部的表示方式。这个过程 与其所依附的.cpp文件并无关系,编译器接着可以读入.cpp文件并同样解析成内部表示,然后把两段内部表示拼接起来,再进行接下来的操作。既然编译两 个.cpp文件都要先对c.h进行解析,那干嘛不把c.h解析好了保存成临时文件,用时读入,不就可以省了一次解析的时间了吗?——预编译头技术节省时间 的原理正在于此,尤其是在这样一个事实下:对源代码的“解析”这个步骤,确实是占了编译时间中很可观的一部分。
我看见你满是狐疑的脸:预处理,就是编译之前的处理,合并.h和.cpp文件分明是预处理的步骤,而解析源代码是编译之中的步骤,先解析后合并?怎么 “预”处理反而跑到编译步骤之后了?这还叫“预”吗?——这个问题我们决定不深究了,毕竟现在的编译器早就混淆了预处理与编译的界限……毕竟,这么做是管 用的,对吗?
我们来看看结果。写一个C++的Hello world,使用cout输出一行字。包含了什么头文件?当然是iostream。这个头文件对于人们来说,绝对是熟视无睹级别的。然而使用它的时候,你 注意到编译器幕后的累累“罪行”了吗?是的,用 -H 参数编译一下这个Hello world吧!看看总共加载了多少个头文件?我的机器上,总共103个!
是的,你应该将它们做成一个.gch文件。如何做?如前所述,再简单不过:只要编译它就可以了:
g++ xxx.h
一句话,就是:把.h文件当成.cpp文件一样来编译。这是最简单的,如果需要控制编译细节,比如常量定义之类,大可加上其它选项。运行之后,你会发现同 个目录里生成了一个名叫xxx.h.gch的文件,这就是我们要的。也许你和我一样,迫不及待地尝试g++ iostream了?呵呵,结果一定是和我一样的失败——在编译.gch的过程中,GCC并没有使用环境变量或 -I 选项来查找被编译的头文件,被编译的头文件必须在当前目录下。然而,被编译的头文件所进一步包含的其它头文件,却可以通过以上途径找到。简言之,就是把直 接编译的那个头文件以类似对待.cpp文件的方式处理了。现在知道该如何编译iostream了吧?对,在当前目录里建立一个头文件,起个随你喜欢的名 字,比如foo.h,在其里面写上:#include <iostream>,然后编译它:g++ foo.h。生成的foo.h.gch,就是我们要的了。其它文件需要用到iostream的,不要包含iostream,要包含foo.h。切记,不是 去包含foo.h.gch!
如果你用过VC,那么这个foo.h也许会让你找到一种似曾相识的感觉吧?对了,就相当于那个 stdafx.h !那么你也该记得,每个文件包含这个foo.h,都应该在文件一开始的地方,否则会出错。真的,终于找到了GCC中的stdafx.h,这种感觉几乎让人 热泪盈眶了^_^
那么接下来,照搬一些stdafx.h相关的注意事项吧,它们同样适用于.gch文件:应该把那些不常修改的(首当其冲,当然是系统的)头文件放在预编译 头里,而那些属于你的程序的一部分的头文件,一般并不放在预编译头里,因为它们可能随时要被修改的。每修改一次就要重新生成预编译头,并没有速度优势可 言,失去预编译头的意义了。另外重要的注意事项是:如果你生成预编译头的时候用了一些选项,比如宏定义,那么使用这个预编译头的其它源代码文件,被编译的 时候也要使用这些选项,否则会因为不匹配而编译失败。
对了,说了半天,从来没有正面讲过如何使用已经生成的预编译头。然而看到这里也该明白了,是的,很简单,只要包含其所对应的.h文件即可!比如你有个头文 件叫foo.h,另外有一大堆其它文件都包含了这个foo.h,原来没有使用预编译头技术,现在忽然想使用了,于是把foo.h编译成了 foo.h.gch。那其它文件要做怎样的修改?——什么都不用,一切照旧!聪明的GCC编译器在查找一个.h文件之前,会自动查找其目录里有没有对应 的.gch文件,如有,且可用,则用之;没有,才用到真正的.h头文件。——慢着,“如有,且可用”,什么叫“可用”?——就是指这个.gch格式要正 确,版本要兼容,而且如上所述,编译两者要用同样的选项。如果.gch不可用,编译器会给出一条警告,告诉我们:这个预编译头不能用!我只好用原有的.h 头文件啦!什么?你说看不到这个警告?——当然,要先打开 -Winvalid-pch 选项才行,其默认是关闭的。
用 -H 选项感受一下预编译头的清爽吧!再没有滚不完的头文件了,明显提高的速度,绝对会让你有种翻身解放的感觉,原来MinGW也可以和蜗牛般的速度说再见的。
笔者后记:有一次同事不小心生成了.gch,但是由于编译选项不一致,导致后面无法编译。小心地雷啊
最近写代码,遇见了一个问题,就是在.h文件中定义了一个宏函数,在相关的.c文件中调用,结果编译出现链接出错,找了好久,终于找出了问题所在,在这里记录一下。
首先、说说预编译的好处:就是提高编译速度
其次、预编译头文件可以用在一下场合:
1、提供某种固定功能和不变的类型定义
2、减少编译的时间
接着、说说預编译可能带来的问题:
在预编译中(gcc)会将.h编译成.gch文件,按理说只要.h文件改动就应该重新编译.gch文件,
但如果没有将.h文件设置为.gch的依赖性的话,那么就会因为不会同步更新而导致编译问题(我就在这里出错)。
最后、说说解决方法:
删除与改动的.h相关文件的.gch文件
以下是官方文档:
3.20 Using Precompiled Headers
Often large projects have many header files that are included in everysource file. The time the compiler takes to process these header filesover and over again can account for nearly all of the time required tobuild the project. To make builds faster, GCC allows users to`precompile' a header file; then, if builds can use the precompiledheader file they will be much faster.
To create a precompiled header file, simply compile it as you would anyother file, if necessary using the -x option to make the drivertreat it as a C or C++ header file. You will probably want to use atool like make to keep the precompiled header up-to-date whenthe headers it contains change.
A precompiled header file will be searched for when #include
isseen in the compilation. As it searches for the included file(see Search Path) thecompiler looks for a precompiled header in each directory just before itlooks for the include file in that directory. The name searched for isthe name specified in the #include
with `.gch' appended. Ifthe precompiled header file can't be used, it is ignored.
For instance, if you have #include "all.h"
, and you haveall.h.gch in the same directory as all.h, then theprecompiled header file will be used if possible, and the originalheader will be used otherwise.
Alternatively, you might decide to put the precompiled header file in adirectory and use -I to ensure that directory is searchedbefore (or instead of) the directory containing the original header. Then, if you want to check that the precompiled header file is alwaysused, you can put a file of the same name as the original header in thisdirectory containing an #error
command.
This also works with -include. So yet another way to useprecompiled headers, good for projects not designed with precompiledheader files in mind, is to simply take most of the header files used bya project, include them from another header file, precompile that headerfile, and -include the precompiled header. If the header fileshave guards against multiple inclusion, they will be skipped becausethey've already been included (in the precompiled header).
If you need to precompile the same header file for differentlanguages, targets, or compiler options, you can instead make adirectory named like all.h.gch, and put each precompiledheader in the directory, perhaps using -o. It doesn't matterwhat you call the files in the directory, every precompiled header inthe directory will be considered. The first precompiled headerencountered in the directory that is valid for this compilation willbe used; they're searched in no particular order.
There are many other possibilities, limited only by your imagination,good sense, and the constraints of your build system.
A precompiled header file can be used only when these conditions apply:
- Only one precompiled header can be used in a particular compilation.
- A precompiled header can't be used once the first C token is seen. Youcan have preprocessor directives before a precompiled header; you caneven include a precompiled header from inside another header, so long asthere are no C tokens before the
#include
.
- The precompiled header file must be produced for the same language asthe current compilation. You can't use a C precompiled header for a C++compilation.
- The precompiled header file must have been produced by the same compilerbinary as the current compilation is using.
- Any macros defined before the precompiled header is included musteither be defined in the same way as when the precompiled header wasgenerated, or must not affect the precompiled header, which usuallymeans that they don't appear in the precompiled header at all.
The -D option is one way to define a macro before aprecompiled header is included; using a #define
can also do it. There are also some options that define macros implicitly, like-O and -Wdeprecated; the same rule applies to macrosdefined this way.
- If debugging information is output when using the precompiledheader, using -g or similar, the same kind of debugging informationmust have been output when building the precompiled header. However,a precompiled header built using -g can be used in a compilationwhen no debugging information is being output.
- The same -m options must generally be used when buildingand using the precompiled header. See Submodel Options,for any cases where this rule is relaxed.
- Each of the following options must be the same when building and usingthe precompiled header:
-fexceptions
- Some other command-line options starting with -f,-p, or -O must be defined in the same way as whenthe precompiled header was generated. At present, it's not clearwhich options are safe to change and which are not; the safest choiceis to use exactly the same options when generating and using theprecompiled header. The following are known to be safe:
-fmessage-length= -fpreprocessed -fsched-interblock
-fsched-spec -fsched-spec-load -fsched-spec-load-dangerous
-fsched-verbose=number -fschedule-insns -fvisibility=
-pedantic-errors
For all of these except the last, the compiler will automaticallyignore the precompiled header if the conditions aren't met. If youfind an option combination that doesn't work and doesn't cause theprecompiled header to be ignored, please consider filing a bug report,see Bugs.
If you do use differing options when generating and using theprecompiled header, the actual behavior will be a mixture of thebehavior for the options. For instance, if you use
-g togenerate the precompiled header but not when using it, you may or maynot get debugging information for routines in the precompiled header.