浅谈GCC预编译头技术

文/jorge

——谨以此文,悼念我等待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也可以和蜗牛般的速度说再见的。

浅谈GCC预编译头技术相关推荐

  1. GCC编译优化应用预编译头

    服务器编译优化记录 对项目编译优化过程中一些思路和脚本工具实现.对内存受限的编译环境有一些帮助. 工具: https://github.com/wangxiaobai-dd/GccPrecompile ...

  2. 使用预编译头提高编译速度

    什么是预编译头 在介绍预编译头之前,有必要了解一下C/C++的编译方式.C/C++的编译单元是源文件(带有.c..cc..cpp等扩展名的文件),在编译一个源文件之前,预处理器会把这个源文件中所有通过 ...

  3. stdafx.h预编译头

    转自http://blog.csdn.net/qingkong8832/article/details/6695123 stdafx.h 1名称的英文全称为:Standard Application  ...

  4. 在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include “pch.h“”?

    编译的时候遇到这个问题: 解决方法: 打开项目属性: 改为不使用预编译头: 这样就好了 还有一种方法: 报错需要什么头文件就包含哪些头文件

  5. 命令行选项“/source-charset”与预编译头不一致

    命令行选项"/source-charset"与预编译头不一致 解决方法:不使用预编译头:

  6. fatal error C1083: 无法打开预编译头文件:“Debug\opencv.pch”: No such file or directory

    fatal error C1083: 无法打开预编译头文件:"Debug\opencv.pch": No such file or directory 步骤/方法: 右键点击你创建 ...

  7. VC++ 使用预编译头

    一.使用默认的预编译头        要使用预编译头,我们必须指定一个头文件,这个头文件包含我们不会经常改变的代码和其他的头文件,然后我们用这个头文件来生成一个预编译头文件(.pch文件),想必大家都 ...

  8. fatal error C1010: 在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include stdafx.h”?

    头文件没有使用预编译头 解决方法: 头文件上单击右键–>属性–>C/C++–>预编译头–>不使用预编译头

  9. vc编程中出现 fatal error C1010: 在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include stdafx.h”?...

    源文: http://blog.csdn.net/monzart/article/details/6030999 解决办法 菜单--〉项目--〉设置,出现"项目设置"对话框,左边展 ...

最新文章

  1. Spring源码分析【6】-ThreadLocal的使用和源码分析
  2. 关于python中使用pyqt5时出现:Process finished with exit code -1073741819 (0xC0000005)
  3. java1.5多线程_【Java多线程】JDK1.5并发包API杂谈
  4. python编写的软件界面-用Python写一个带图形界面的文件压缩软件
  5. SQL Server中如何取得刚插入的标识值
  6. Android中全屏或者取消标题栏
  7. oracle9i用expdp导出全库,Linux下Oracle 11g数据库全库自动备份(EXPDP)
  8. Druid 连接池的实用 配置详解
  9. 三星i9018root方法
  10. java threadstatus_Thread之一:线程生命周期及六种状态
  11. IOS之提示Interface type cannot be statically allocated
  12. 【Pytorch神经网络理论篇】 04 Variable类型与自动微分模块剖析
  13. 堆排序的Python实现
  14. WIN7激活工具拒绝访问cannot open file C:\OEMSF解决方法
  15. Nginx无证书反向代理
  16. Allegro 小知识总结
  17. HTML页面为什么设置了UTF-8仍然中文乱码
  18. SQLZOO总结3-5
  19. Codeforces 892A. Greed
  20. WifiManager详解

热门文章

  1. toolbar ,textfield,图片拉伸,Bundle
  2. 搜索引擎ElasticSearchV5.4.2系列二之ElasticSearchV5.4.2+kibanaV5.4.2+x-packV5.4.2安装
  3. redis 在 php 中的应用(事务 [ Transaction ] 篇)
  4. 06章 映射一对多双向关联关系、以及cascade、inverse属性
  5. TCP的三次握手和四次分手
  6. linux定时器(crontab)实例
  7. C++ stl vector介绍
  8. 小D学blend-----如何创建自定义的Tooltip控件
  9. java 8 lambda reduce_JDK8新特性Lambda表达式体验
  10. python怎么做彩票概率_用Python一次性把论文作图与数据处理全部搞定!