首页 > 浅谈GCC预编译头技术

浅谈GCC预编译头技术

浅谈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 ,然后编译它: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也可以和蜗牛般的速度说再见的。

更多相关:

  • PCL1.9.1并没有支持vs2015版本的exe版本,然后需要下载PCL的源码重新自己CMake编译出vs2015版本的编译的目的:1、得到支持vs2015的版本的PCL1.9.1 前期准备1、下载PCL源码:https://github.com/PointCloudLibrary/pcl2、下载安装版(主要是为...

  • 下载gcc文档,第三章有详细的build options的介绍。 最近我用到2个关键的option 来定位问题,简单介绍一下: 1. -E: 只是进行预编译,不会编译和link。用于检查宏在代码中的展开是否符合预期; 2.--verbose: 开启verbose mode, 用于输出编译整个过程中的详细信息,可帮助查看build...

  • 在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码,我们就称这种编译器支持交叉编译。这个编译过程就叫交叉编译。简单地说,就是在一个平台上生成另一个平台上的可执行代码。这里需要注意的是所谓平台,实际上包含两个概念:体系结构(Architecture)、操作系统(Operating System)。同一个体系结构可以运行...

  • 文章目录前言Makefile 编译流程1. 平台变量/环境变量的初始化。2. 编译需要的源码文件变量初始化。3. include 目录的设置。4. 编译的执行逻辑。问题记录1:可能的打包命令`ar` 失效问题5. 执行具体的编译指令问题记录2: jar 包编译...

  • 本文来自 运维人生 ,作者:fly是个稻草人链接:http://www.ywadmin.com/?id=76误删除linux系统文件了?不用急,本文将给你一个恢复linux文件的方法,让你轻松应对运维中的各风险问题。方法总比问题多~说在前面的话针对日常维护操作,难免会出现文件误删除的操作。大家熟知linux文件系统不同win有回收...

  • 原文来自SecIN社区—作者:WiHat0x00 什么是WebShell渗透测试工作的一个阶段性目标就是获取目标服务器的操作控制权限,于是WebShell便应运而生。Webshell中的WEB就是web服务,shell就是管理攻击者与操作系统之间的交互。Webshell被称为攻击者通过Web服务器端口对Web服务器有一定的操作权限,而...

  • 断电时文件系统发生了什么?硬盘又发生了什么?下一次开机时写到一半的文件在系统层面还在吗?在底层还在吗?更进一步的, 文件系统如何保证事务性, 会不会存在某种极端情况导致例如最后几个bit还没写完, 文件系统却认为它成功了的情况?回答不限任何文件系统,谢谢!下面是「北极」的回复分享断电的一瞬间,很多事情是无法确定的:1. 你无法确定...

  • 接到项目需求。需要搭建一个页面进行交互,慢慢来b (2).jpg使用python django框架进行页面的搭建在项目文件下打开窗口,输入命令;django-admin startproject helloword#在文件helloword/helloword/创建view.py在view.py文件中输入以代码from django....

  • 常见的错误集合解决方案(一)No.1提示错误'Microsoft.VC90.CRT,version="9.0.21022.8"把Microsoft.NET Framework 3.5.1下面的全部勾选上。No.2解决Qt Designer设计的图标但是VS生成不显示问题描述:在Qt designer中为菜单栏和工具栏设计的图标,但是...

  • 一、代码结构概览1.核心部分configs:储存各种网络的yaml配置文件datasets:存放数据集的地方detectron2:运行代码的核心组件tools:提供了运行代码的入口以及一切可视化的代码文件。2.Tutorial部分demo:显而易见就是demodocs: 同样显而易见。。tests:提供了一些测试代码projects:...

  •     我刚刚接手这个项目的时候就被一系列不知所措的文件命名给深深的震惊了,那种振聋发聩不亚于听到赌王离世的消息。 首先请看,文件本来是用于处理“请假审批”,但是文件名居然叫做“teaApprove”,不要欺负我的初中英语不好,这个teaApprove我第一个感觉就是和“喝茶、茶叶”有关的业务,可是和我们这个项目八竿子打不着...

  • 这个问题简单,不做过多描述,如题所述,如果因为这个导致错误,请安装 npm install stylus-loader css-loader style-loader -D...

  • 使用这个宏TS_VERSION_MAOR来判断,这个宏定义在编译时生成在apidefs.h,它包含在ts/ts.h中,所以请在插件这包含...

  • linux valgrind Memcheck–内存检查工具 使用方法: 注意,这里要用debug版本,如果是release的运行文件,则用debug编译出来的可执行文件替换 输出到终端: valgrind --tool=memcheck --leak-check=full ./test.out 输出到文件: valgri...