`

浅谈GCC预编译头技术

 
阅读更多

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

 

转自 http://lych.yo2.cn/articles/%E6%B5%85%E8%B0%88gcc%E9%A2%84%E7%BC%96%E8%AF%91%E5%A4%B4%E6%8A%80%E6%9C%AF.html

分享到:
评论

相关推荐

    MCU编译与运行浅谈

    ### MCU编译与运行浅谈 #### 一、编译和汇编 在电子工程领域,尤其是单片机(MCU)开发过程中,从编写代码到最终执行的过程中涉及到多个关键步骤,其中包括编译、汇编、链接、加载以及启动等过程。本文将重点介绍...

    浅谈面向云计算数据中心的新型解压缩方法.pdf

    然后通过GCC编译器和Altera SDK for OpenCL(AOC)高层次综合工具进行编译和综合,分别生成可执行程序文件和可在FPGA上运行的AOCX文件。这一过程显著减少了算法实现的开发周期。 该方法的关键点在于通过合理设计...

    浅谈嵌入式Linux操作系统的开发.pdf

    "浅谈嵌入式Linux操作系统的开发" 嵌入式Linux操作系统是指以应用为中心、以计算机技术为基础,软硬件可裁剪、适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。嵌入式系统主要由它是集软...

    浅谈Codewarrior局部优化技巧

    通过使用预编译指令#pragma,开发者可以在两种编译器中进行局部定制化优化。以GCC为例,可以使用#pragma GCC push_options和#pragma GCC pop_options来开启和结束编译器选项。 使用#pragma指令后,可以通过...

    浅谈Linux环境下gcc优化级别

    以笔者多年的linux c开发经验来说优化通常分为两个方面,一是人为优化,也就是基于编程经验采用更简易的数据结构函数等来降低编译器负担,二是采用系统自带的优化模式,也就是gcc – o系列,下面我将简述一下各级...

    浅谈C语言基础:入门与实践

    使用编译器如GCC或Clang进行编译,利用GDB等工具进行调试,是提升技能的关键。 总结,C语言基础的学习包括理解语言的基本概念、掌握控制结构、熟悉数据类型和操作符,以及熟练运用函数和指针。通过实际编程项目,将...

    浅谈IPP嵌入式音频解码器的设计与优化.docx

    标题中的“浅谈 IPP 嵌入式音频解码器的设计与优化”指的是对使用 Intel IPP(Integrated Performance Primitives)库在嵌入式系统中构建高效音频解码器的探讨和改进方法。IPP 是 Intel 提供的一个高性能软件库,...

    浅谈linux kernel对于浮点运算的支持

    对于带有FPU的处理器,如X86架构,Linux内核默认使用`-msoft-float`编译选项,这意味着内核编译为软浮点程序,即依赖于GCC编译器模拟浮点运算。然而,这并不意味着内核本身的性能受到影响,因为内核代码中通常较少...

    浅谈手动书写Makefile与自动生成Makefile1

    在嵌入式系统开发(如ARM Linux)中,常常需要进行交叉编译,即在一种架构的系统上编译另一架构的目标代码。`CROSSCOMPILE`变量用于指定交叉编译器前缀,如`arm-linux-gcc`。 5. **Makefile的最佳实践**: - 使用...

    非计算机专业“数据结构”课程教学方法浅谈.pdf

    在教学过程中,教授学生如何使用GCC编译和链接程序,以及如何使用GDB进行程序调试是十分重要的。这将帮助学生更好地理解和掌握程序运行时的状态,学会诊断和修复程序中的错误。 3. LATEX排版系统: LATEX是一种...

    浅谈嵌入式的学习步骤及方法.pdf

    此外,还需要熟悉编译器GCC、调试器GDB和Makefile编写,这些都是在Linux环境下进行程序编译、调试和项目管理的利器。 三、Linux系统编程 Linux系统编程关注于标准I/O库,多进程和多线程的创建与管理,以及进程间...

    浅谈嵌入式的学习步骤及方法

    这包括对Linux操作系统的概念、安装方法有所了解,熟悉Linux目录结构、基本命令、编辑器VI,编译器GCC,调试器GDB和Make项目管理工具等。此外,深入学习Shell脚本编写和Linux下的Makefile也是构建嵌入式开发环境所...

    浅谈在Redhat9上安装Oracle92.docx

    然而,由于Redhat 9.0采用了较高版本的GCC编译器,导致一些依赖于较低版本GCC的软件(如Oracle 9i)在安装过程中可能会遇到兼容性问题。本文将详细介绍如何在Redhat 9.0上成功安装Oracle 9i。 #### 二、系统准备 ##...

    浅谈Linux C语言动态库及静态库

    **静态库** 是一组预编译的对象文件的集合,它在程序链接阶段会被合并到可执行文件中。这意味着最终的可执行文件包含了库的所有代码,不依赖于运行时的外部库。静态库的创建通常使用`ar`工具。例如,在给定的示例中...

    浅谈Linux的库文件

    而动态库的创建则需要使用`gcc`的`-shared`选项,同时可能需要`-fPIC`参数以生成位置独立代码,并通过`-Wl,-soname`指定库的软链接名。 库文件的命名有一定的规范。静态库通常以`lib`开头,如`libxxxx.a`,其中`...

    浅谈C语言中的强符号、弱符号、强引用和弱引用

    首先,强符号是那些在编译时被定义并且初始化的全局变量或者函数。在C/C++中,函数默认被视为强符号,因为它们的实现是唯一的。如果在多个文件中定义了同名的强符号,链接器会在链接阶段报错,因为它无法确定应该...

Global site tag (gtag.js) - Google Analytics