文章标题

来源:互联网 发布:centos 6.5搭建lnmp 编辑:程序博客网 时间:2024/06/05 03:07
其 实刚开始编程的时候,我是丝毫不重视编译速度之类的问题的,原因很简单,因为那时我用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,但是由于编译选项不一致,导致后面无法编译。小心地雷啊///////////////////////////////////////GNU CC 从 3.4.x 版和 4.x 版开始,也支持了这种提高编译效率的机制。只是由于 GNU CC 的手册中的《Using Precompiled Headers》一节对此介绍不多,也没有简单的自动项目管理工具支持这项功能,因而许多网友还不知道 GNU CC 的这项功能。GNU CC 的手册中建议使用 make 管理预编译头文件,还指出 C 语言的预编译头文件和 C++ 语言的预编译头文件是不一样的。这里首先讲述项目中只有 C 语言源文件或只有 C++ 语言源文件的情形,再讲述项目中两种语言的源文件同时存在的情况。项目中只有 C 或 C++ 一种语言的源文件时,只需建立一个预编译头文件。建立一个头文件,例如命名为 inc.h。该文件供项目中所有的 C/C++ 源文件使用。将整个项目所需要的头文件都列在其中:/* $FreeBSD$ */#ifndef _INC_H_#define _INC_H_#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <sys/types.h>#include <sys/uio.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#endif /* ! _INC_H_ *建立 Makefile,以维护预编译头文件。一方面要建立维护 GNU CC 的预编译头文件 inc.h.gch 的规则;另一方面,要在编译每个 C/C++ 源文件时检查 inc.h.gch,即让所有 .o 文件依赖于 inc.h.gch# $FreeBSD$CC  =   gccCFLAGS  =   -g -WallCXX =   gccCXXFLAGS    =   -g -WallLD  =   gccLDFLAGS =   -g -WallEXE =   testappPCH_H   =   inc.hPCH =   inc.h.gchSRCS    =   testapp.cOBJS    =   testapp.oLIBS    =           # System LibrariesECHO    =   echoCP  =   cp -vRM  =   rm -f.SUFFIXES:.SUFFIXES: .o .c .cxx# The meaning of "$<":#     BSD Pmake: the implied source#     GNU make: the first prerequisite.c.o:    $(CC) $(CFLAGS) -c $<.cxx.o:    $(CXX) $(CXXFLAGS) -c $<all:    $(EXE)#                  $>                    $^# BSD Pmake    all sources        not defined# GNU make     not defined        all prerequisites# Both interpret "$@" as target$(EXE):$(OBJS) $(LIBBDD)    $(LD) $(LDFLAGS) -o $@ $> $^ $(LIBS)# Pre-compiled header$(OBJS): $(PCH)$(PCH): $(PCH_H)    $(CC) $(CFLAGS) $> $^clean:    $(RM) $(PCH) $(OBJS)# For Both UNIX-like OS and Microsoft Windows (MinGW/Cygwin)    $(RM) $(EXE) $(EXE).exe如果项目既包含 C 语言源文件,也包含 C++ 语言源文件,就需要为两种语言分别维护一个预编译头文件。再建立一个头文件,例如命名为 inc.hppinc.h 供 C 语言源文件使用,而 inc.hpp 供 C++ 语言文件使用。假如 inc.hpp 的内容与 inc.h 的相同,只需要简单的写上:/* $FreeBSD$ */#ifndef _INC_HPP_#define _INC_HPP_#include "inc.h"#endif /* ! _INC_HPP_ */在 Makefile 里也要随之增加对 inc.hpp 的维护。一是要增加产生 inc.hpp.gch 的规则,此时执行 GNU CC 时要增加参数“-x c++-header”;二是要在 clean 一节中删除这个预编译头文件。# $FreeBSD$CC  =   gccCFLAGS  =   -g -WallCXX =   gccCXXFLAGS    =   -g -WallLD  =   gccLDFLAGS =   -g -WallEXE =   testappPCH_H   =   inc.hPCH =   inc.h.gchPCH_X_H =   inc.hppPCH_X   =   inc.hpp.gchSRCS    =   testapp.cOBJS    =   testapp.oLIBS    =           # System LibrariesECHO    =   echoCP  =   cp -vRM  =   rm -f.SUFFIXES:.SUFFIXES: .o .c .cxx# The meaning of "$<":#     BSD Pmake: the implied source#     GNU make: the first prerequisite.c.o:    $(CC) $(CFLAGS) -c $<.cxx.o:    $(CXX) $(CXXFLAGS) -c $<all:    $(EXE)#                  $>                    $^# BSD Pmake    all sources        not defined# GNU make     not defined        all prerequisites# Both interpret "$@" as target$(EXE):$(OBJS) $(LIBBDD)    $(LD) $(LDFLAGS) -o $@ $> $^ $(LIBS)# Pre-compiled header$(OBJS): $(PCH)$(PCH): $(PCH_H)    $(CC) $(CFLAGS) $> $^$(PCH_X): $(PCH_X_H)    $(CXX) $(CXXFLAGS) -x c++-header $> $^clean:    $(RM) $(PCH) $(PCH_X) $(OBJS)# For Both UNIX-like OS and Microsoft Windows (MinGW/Cygwin)    $(RM) $(EXE) $(EXE).exe这是以上两种 Makefile 的比较:@@ -12,6 +12,8 @@ EXE    =   testapp PCH_H  =   inc.h PCH    =   inc.h.gch+PCH_X_H    =   inc.hpp+PCH_X  =   inc.hpp.gch SRCS   =   testapp.c OBJS   =   testapp.o LIBS   =           # System Libraries@@ -48,7 +50,10 @@ $(PCH): $(PCH_H)    $(CC) $(CFLAGS) $> $^+$(PCH_X): $(PCH_X_H)+   $(CXX) $(CXXFLAGS) -x c++-header $> $^+ clean:-   $(RM) $(PCH) $(OBJS)+   $(RM) $(PCH) $(PCH_X) $(OBJS) # For Both UNIX-like OS and Microsoft Windows (MinGW/Cygwin)    $(RM) $(EXE) $(EXE).exe
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 孕妇胃疼怎么办4个月了 25岁欠了5万块钱怎么办 感冒嗓子疼怎么办最简单的方法 和老婆离婚了我的心好痛怎么办 4s店不给退定金怎么办 教你闪腰了后该怎么办 coolpad酷派手机开不了机怎么办 苹果5s黑屏开不了机怎么办 苹果4s的屏坏了怎么办 苹果6手机充电口接触不良怎么办 5s用久了卡顿怎么办 孕妇血糖高怎么办什么方法降最好 脚砸了肿了紫了怎么办 我想在淘宝上卖东西该怎么办 苹果手机4s开不了机怎么办 冒险岛s前出2条怎么办 狗狗又吐又拉血怎么办 小孩上网成瘾怎么办父母要怎么做 一只眼睛大一只眼睛小怎么办 带近视镜时间长了眼睛变形怎么办 联通卡2g换4g卡怎么办 上火牙疼怎么办教你立刻止疼 吃热的凉的牙疼怎么办 我买的股票退市了怎么办 如果起诉离婚另一方不出庭怎么办 10个月宝宝还没长牙怎么办 超敏c反应蛋白>5怎么办 怀孕才两个月肚子就大了怎么办 腰椎间盘突出腿疼厉害怎么办 打了促排卵针不排卵怎么办 孕34周隐血1十是怎么办 窦性心动过缓伴不齐怎么办 09年买的万科b怎么办 苹果5s手机打不开机怎么办 剖腹产后一年半后意外怀孕怎么办 考驾照挂了5次了怎么办 怀孕咳嗽一个月了好不了怎么办 孕妇餐后2小时血糖高怎么办 我想开网店但我不懂该怎么办 新开的淘宝店没生意怎么办 做肝胆b超喝了水怎么办