预编译头是什么?
许多初学 VC 的朋友也许都为那么一个问题困扰过:
为什么所有的 cpp 都必须 #include "stdafx.h"
也许请教了别的高手之后,他们会告诉你,这是预编译头文件,必须包含。可是,这到底是为什么呢?预编译头有什么用呢?
这得从头文件的编译原理讲起。其实头文件并不神秘,它的全部作用,就是把自己的所有内容直接“粘贴”到相应的 #include 语句处。如果不相信的话,不妨做个实验,将一个 cpp 中的所有 #include 语句删掉,并将它包含的文件粘贴到相应的位置,你会发现,文件的编译和运行都完全没有受到影响。其实,编译器在编译你的程序的时候,所做的第一件事,也就是展开所有#include 语句和 #define 语句。
头文件的出现,固然给书写程序带来了很大方便。可是到了 Windows 时代后,慢慢就呈现出一些问题了。几乎所有的 Windows 程序都必须包含 windows.h,而那个文件却硕大无比,将它展开后往所有文件中一粘贴,编译的时候立刻慢得像只蜗牛。
到了 MFC 时代后,情况更为恶劣了。毕竟 C 风格的 Windows 头文件里面包含的还仅仅是函数定义和宏,编译难度不算太大,而 MFC 库里面的头文件可都是类声明啊!更何况,一个最简单的工程,都会生成大量的类,需要用到大量的函数。如果工程稍微复杂一些,编译难度可想而知!
但是,人们惊奇地发现,虽然用到的头文件又多又杂,但是在一个工程中,总有那么一堆头文件,是几乎所有 cpp 都必须包含的。那么,可不可以把这些头文件提取出来,只编译一遍,然后所有其它 cpp 就都能使用呢?没错,这就是预编译头的思想都由来!
因此,所谓的预编译头就是把一个工程中经常被用到的头文件,预先编译好放在一个pch文件里,以后用到就不必再次编译,这个pch文件就称为预编译头,但必须注意:预编译头中的代码是必须是稳定的,在工程开发的过程中不会经常改变的。如果这些代码被修改,则需要重新编译预编译头文件。注意生成预编译头是很耗时间的。
比如,我们来考察一个典型的由AppWizard生成的MFC Dialog Based程序的预编译头文件,我们会发现这个头文件里包含了以下的头文件:
#include <afxwin.h> // MFC core and standard components
#include <afxext.h> // MFC extensions
#include <afxdisp.h> // MFC Automation classes
#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls
#include <afxcmn.h>
这些正是使用MFC必须包含的头文件,同时我们也不太可能在我们的工程中修改这些头文件的,所以说他们是稳定的。满足1)经常用到,2)稳定两个特点,应该被包含到预编译头文件中。
同时你得注意预编译头通常很大,通常有6-7M大。注意及时清理那些没有用的预编译头。
实践证明,使用了预编译头技术后,编译速度大大提高了。可以到你的工程目录下的Debug 或 Release 目录中看一看,里面有一个体积极为硕大的 .pch 文件,那就是传说中的“编译之后的预编译头”。
使用了预编译头技术后,虽然带来了极大地方便,但也造成了一个问题:由于它假定预编译头中包含过的头文件会在所有 cpp 中使用,因此它在编译你的 cpp 的时候,就会将预编译头中已经编译完的部分加载到内存中。如果它突然发现你的 cpp 居然没有包含预编译头,它就会很郁闷,因为它不知道该如何将已编译完的部分从内存中请出去,整个编译过程就会失败。因此,如果你使用了预编译头技术,就必须在所有的 cpp 中包含预编译头。
MFC 工程中为你建立了一个默认的预编译头 stdafx.h,如果你愿意,也可以在自己的工程中使用其它文件名作为你的预编译头,如果你觉得有必要。
预编译头文件的使用:
现在开始介绍VS2010的预编译功能的使用,由于预编译详细使用比较的复杂,这里只介绍几个最重要的预编译指令: /Yu, /Yc, /Fp。
想必大家都知道 StdAfx.h这个默认的预编译头文件。其实预编译文件可以是任何名字的,如果觉得有必要,可以自己修改名字。
我们如何指定StdAfx.h来生成预编译头呢?我们知道一个头文件是不能编译的。所以我们还需要一个cpp文件来生成pch 文件。这个文件默认的就是StdAfx.cpp。在这个文件里只有一句代码就是:#include “Stdafx.h”。原因是理所当然的,我们仅仅是要它能够编译而已―――也就是说,要的只是它的.cpp的扩展名。我们可以用/Yc编译开关来指定StdAfx.cpp生成一个.pch文件,通过/Fp编译开关来指定生成的pch文件的名字。
下面给出一个使用预编译头文件的操作步骤, 享受一下预编译头文件给我们带来的编译速度的提升:
1) 添加一个stdafx.h文件(名字随便取, 这里用了VS默认提供的名称), 在这个.h文件里include要使用的头文件(一般是系统提供的库文件, 自己写的不常变的头文件也可以加进来)
2) 添加一个stdafx.cpp文件, 并include "stdafx.h"
3)项目属性-->c/c++-->预编译头设置为/Yu(Use Precompiled Header), stdafx.h,/Fp指定创建的预编译头所在的路径和名称
4)stdafx.cpp属性-->c/c++->预编译头设置为/Yc(Create Precompiled Header), stdafx.h,/Fp指定创建的预编译头所在的路径和名称
这样,我们就设置好了预编译头文件。也就是说,我们可以使用预编译头功能了。以下是注意事项:
1)如果某cpp文件使用了/Yu,就是说使用了预编译,我们在该cpp文件的最开头,我强调一遍是最开头,包含你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如果你没有包含这个文件,就告诉你Unexpected file end. 如果你不是在最开头包含的,你自己试以下就知道了,绝对有很惊人的效果…..
2)如果你把pch文件不小心丢了,根据以上的分析,你只要让编译器再生成一个pch文件就可以了。也就是说把 stdafx.cpp(即指定/Yc的那个cpp文件)重新编译一遍就可以了。当然你也可以傻傻的 Rebuild all。简单一点就是选择那个cpp文件,按一下Ctrl + F7就可以了。