预编译指令

#if   ...  
  #else  
  ...  
  #endif//与#if对应  
  作为一个编译“开关”,比如:  
  #if(条件满足)  
  执行代码1  
  #else  
  执行代码2  
  #endif  
  假如编译时,确实满足条件,则生成的程序文件(.exe文件)中不会有执行代码2的。如果用普通if语句,生成的程序文件就会有执行代码2,这个区别看看生成文件大小就可以知道。如果你的条件在程序编译前就已经确定了,那就用#if;如果条件需要在程序运行过程中才能判断,则用if。  

——————————————————————————————————————————————

预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。常见的预编译指令有:
(1)#include 指令
该指令指示编译器将xxx.xxx文件的全部内容插入此处。若用<>括起文件则在系统的INCLUDE目录中寻找文件,若用" "括起文件则在当前目录中寻找文件。一般来说,该文件是后缀名为"h"或"hpp"的头文件。
注意:<>不会在当前目录下搜索头文件,如果我们不用<>而用""把头文件名扩起,其意义为在先在当前目录下搜索头文件,再在系统默认目录下搜索。
(2)#define指令
该指令有三种用法:
第一种是定义标识,标识有效范围为整个程序,形如#define XXX,常与#if配合使用;
第二种是定义常数,如#define max 100,则max代表100(这种情况下使用const定义常数更好,原因见注1);
第三种是定义"函数",如#define get_max(a, b) ((a)>(b)?(a):(b)) 则以后使用get_max(x,y)就可以得到x和y中较大的数(这种方法存在一些弊病,见注2)。
(3)#if、#else和#endif指令
这些指令一般这样配合使用:
#if defined(标识) //如果定义了标识
要执行的指令
#else
要执行的指令
#endif
在头文件中为了避免重复调用(比如说两个头文件互相包含对方),常采用这样的结构:
#if !(defined XXX) //XXX为一个在你的程序中唯一的标识符,
//每个头文件的标识符都不应相同。
//起标识符的常见方法是若头文件名为"abc.h"
//则标识为"abc_h"
#define XXX
真正的内容,如函数声明之类
#endif
注1:因为:const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误(边际效应)。
注2:例如get_max(a++, b)时,a++会被执行多少次取决于a和b的大小!所以建议还是用内联函数而不是这种方法提高速度。虽然有这样的弊病,但这种方法的确非常灵活,因为a和b可以是各种数据类型。

——————————————————————————————————

(一) 预处理命令简介


预处理命令由#(hash字符)开头, 它独占一行, #之前只能是空白符. 以#开头的语句就是预处理命令, 不以#开头的语句为C中的代码行. 常用的预处理命令如下:

#define              定义一个预处理宏

#undef               取消宏的定义

#include            包含文件命令

#include_next   与#include相似, 但它有着特殊的用途

#if                      编译预处理中的条件命令, 相当于C语法中的if语句

#ifdef                判断某个宏是否被定义, 若已定义, 执行随后的语句

#ifndef             与#ifdef相反, 判断某个宏是否未被定义

#elif                  若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif之后的语句, 相当于C语法中的else-if

#else                与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else之后的语句, 相当于C语法中的else

#endif              #if, #ifdef, #ifndef这些条件命令的结束标志.

defined            与#if, #elif配合使用, 判断某个宏是否被定义

#line                标志该语句所在的行号

#                      将宏参数替代为以参数值为内容的字符窜常量

##                   将两个相邻的标记(token)连接为一个单独的标记

#pragma        说明编译器信息

#warning       显示编译警告信息

#error            显示编译错误信息

(二) 预处理的文法


预处理并不分析整个源代码文件, 它只是将源代码分割成一些标记(token), 识别语句中哪些是C语句, 哪些是预处理语句. 预处理器能够识别C标记, 文件名, 空白符, 文件结尾标志.

预处理语句格式:    #command name(...) token(s)

1, command预处理命令的名称, 它之前以#开头, #之后紧随预处理命令, 标准C允许#两边可以有空白符, 但比较老的编译器可能不允许这样. 若某行中只包含#(以及空白符), 那么在标准C中该行被理解为空白. 整个预处理语句之后只能有空白符或者注释, 不能有其它内容.

2, name代表宏名称, 它可带参数. 参数可以是可变参数列表(C99).

3, 语句中可以利用"\"来换行.

e.g.

#  define  ONE 1 /* ONE == 1 */

等价于: #define ONE 1

#define err(flag, msg) if(flag) \

    printf(msg)

等价于: #define err(flag, msg) if(flag) printf(msg)

(三) 预处理命令详述


1, #define

#define命令定义一个宏:

#define MACRO_NAME(args) tokens(opt)

之后出现的MACRO_NAME将被替代为所定义的标记(tokens). 宏可带参数, 而后面的标记也是可选的.

对象宏

不带参数的宏被称为"对象宏(objectlike macro)"

#define经常用来定义常量, 此时的宏名称一般为大写的字符串. 这样利于修改这些常量.

e.g.

#define MAX 100

int a[MAX];

#ifndef __FILE_H__

#define __FILE_H__

#include "file.h"

#endif

#define __FILE_H__ 中的宏就不带任何参数, 也不扩展为任何标记. 这经常用于包含头文件.

要调用该宏, 只需在代码中指定宏名称, 该宏将被替代为它被定义的内容.

函数宏

带参数的宏也被称为"函数宏". 利用宏可以提高代码的运行效率: 子程序的调用需要压栈出栈, 这一过程如果过于频繁会耗费掉大量的CPU运算资源. 所以一些代码量小但运行频繁的代码如果采用带参数宏来实现会提高代码的运行效率.

函数宏的参数是固定的情况

函数宏的定义采用这样的方式: #define name( args ) tokens

其中的args和tokens都是可选的. 它和对象宏定义上的区别在于宏名称之后不带括号.

注意, name之后的左括号(必须紧跟name, 之间不能有空格, 否则这就定义了一个对象宏, 它将被替换为 以(开始的字符串. 但在调用函数宏时, name与(之间可以有空格.

e.g.

#define mul(x,y) ((x)*(y))

注意, 函数宏之后的参数要用括号括起来, 看看这个例子:

e.g.

#define mul(x,y) x*y

"mul(1, 2+2);" 将被扩展为: 1*2 + 2

同样, 整个标记串也应该用括号引用起来:

e.g.

#define mul(x,y) (x)*(y)

sizeof mul(1,2.0) 将被扩展为 sizeof 1 * 2.0

调用函数宏时候, 传递给它的参数可以是函数的返回值, 也可以是任何有意义的语句:

e.g.

mul (f(a,b), g(c,d));

e.g.

#define insert(stmt) stmt

insert ( a=1; b=2;)  相当于在代码中加入 a=1; b=2 .

insert ( a=1, b=2;)  就有问题了: 预处理器会提示出错: 函数宏的参数个数不匹配. 预处理器把","视为参数间的分隔符.  

insert ((a=1, b=2;)) 可解决上述问题.

在定义和调用函数宏时候, 要注意一些问题:

1, 我们经常用{}来引用函数宏被定义的内容, 这就要注意调用这个函数宏时的";"问题.

example_3.7:

#define swap(x,y) { unsigned long _temp=x; x=y; y=_tmp}

如果这样调用它: "swap(1,2);" 将被扩展为: { unsigned long _temp=1; 1=2; 2=_tmp};

明显后面的;是多余的, 我们应该这样调用: swap(1,2)

虽然这样的调用是正确的, 但它和C语法相悖, 可采用下面的方法来处理被{}括起来的内容:

#define swap(x,y) \

    do { unsigned long _temp=x; x=y; y=_tmp} while (0)

swap(1,2); 将被替换为:

do { unsigned long _temp=1; 1=2; 2=_tmp} while (0);

在Linux内核源代码中对这种do-while(0)语句有这广泛的应用.

2, 有的函数宏是无法用do-while(0)来实现的, 所以在调用时不能带上";", 最好在调用后添加注释说明.

eg_3.8:

#define incr(v, low, high) \

    for ((v) = (low),; (v) <= (high); (v)++)

只能以这样的形式被调用: incr(a, 1, 10)  /* increase a form 1 to 10 */

函数宏中的参数包括可变参数列表的情况

C99标准中新增了可变参数列表的内容. 不光是函数, 函数宏中也可以使用可变参数列表.

#define name(args, ...) tokens

#define name(...) tokens

"..."代表可变参数列表, 如果它不是仅有的参数, 那么它只能出现在参数列表的最后. 调用这样的函数宏时, 传递给它的参数个数要不少于参数列表中参数的个数(多余的参数被丢弃).

通过__VA_ARGS__来替换函数宏中的可变参数列表. 注意__VA_ARGS__只能用于函数宏中参数中包含有"..."的情况.

e.g.

#ifdef DEBUG

#define my_printf(...) fprintf(stderr, __VA_ARGS__)

#else

#define my_printf(...) printf(__VA_ARGS__)

#endif

tokens中的__VA_ARGS__被替换为函数宏定义中的"..."可变参数列表.

注意在使用#define时候的一些常见错误:

#define MAX = 100

#define MAX 100;

=, ; 的使用要值得注意. 再就是调用函数宏是要注意, 不要多给出";".

注意: 函数宏对参数类型是不敏感的, 你不必考虑将何种数据类型传递给宏. 那么, 如何构建对参数类型敏感的宏呢? 参考本章的第九部分, 关于"##"的介绍.

关于定义宏的另外一些问题

(1) 宏可以被多次定义, 前提是这些定义必须是相同的. 这里的"相同"要求先后定义中空白符出现的位置相同, 但具体的空白符类型或数量可不同, 比如原先的空格可替换为多个其他类型的空白符: 可为tab, 注释...

e.g.

#define NULL 0

#define NULL /* null pointer */     0

上面的重定义是相同的, 但下面的重定义不同:

#define fun(x) x+1

#define fun(x) x + 1 或: #define fun(y) y+1

如果多次定义时, 再次定义的宏内容是不同的, gcc会给出"NAME redefined"警告信息.

应该避免重新定义函数宏, 不管是在预处理命令中还是C语句中, 最好对某个对象只有单一的定义. 在gcc中, 若宏出现了重定义, gcc会给出警告.

(2) 在gcc中, 可在命令行中指定对象宏的定义:

e.g.

$ gcc -Wall -DMAX=100 -o tmp tmp.c

相当于在tmp.c中添加" #define MAX 100".

那么, 如果原先tmp.c中含有MAX宏的定义, 那么再在gcc调用命令中使用-DMAX, 会出现什么情况呢?

---若-DMAX=1, 则正确编译.

---若-DMAX的值被指定为不为1的值, 那么gcc会给出MAX宏被重定义的警告, MAX的值仍为1.

注意: 若在调用gcc的命令行中不显示地给出对象宏的值, 那么gcc赋予该宏默认值(1), 如: -DVAL == -DVAL=1

(3) #define所定义的宏的作用域

宏在定义之后才生效, 若宏定义被#undef取消, 则#undef之后该宏无效. 并且字符串中的宏不会被识别

e.g.

#define ONE 1

sum = ONE + TWO    /* sum = 1 + TWO  */

#define TWO 2

sum = ONE + TWO    /* sum = 1 + 2    */  

#undef ONE

sum = ONE + TWO    /* sum = ONE + 2  */

char c[] = "TWO"   /* c[] = "TWO", NOT "2"! */

(4) 宏的替换可以是递归的, 所以可以嵌套定义宏.

e.g.

# define ONE NUMBER_1

# define NUMBER_1 1

int a = ONE  /* a = 1 */

2, #undef

#undef用来取消宏定义, 它与#define对立:

#undef name

如够被取消的宏实际上没有被#define所定义, 针对它的#undef并不会产生错误.

当一个宏定义被取消后, 可以再度定义它.

3, #if, #elif, #else, #endif

#if, #elif, #else, #endif用于条件编译:

#if 常量表达式1

    语句...

#elif 常量表达式2

    语句...

#elif 常量表达式3

    语句...

...

#else

    语句...

#endif

#if和#else分别相当于C语句中的if, else. 它们根据常量表达式的值来判别是否执行后面的语句. #elif相当于C中的else-if. 使用这些条件编译命令可以方便地实现对源代码内容的控制.

else之后不带常量表达式, 但若包含了常量表达式, gcc只是给出警告信息.

使用它们可以提升代码的可移植性---针对不同的平台使用执行不同的语句. 也经常用于大段代码注释.

e.g.

#if 0

{

    一大段代码;

}

#endif

常量表达式可以是包含宏, 算术运算, 逻辑运算等等的合法C常量表达式, 如果常量表达式为一个未定义的宏, 那么它的值被视为0.

#if MACRO_NON_DEFINED  == #if 0

在判断某个宏是否被定义时, 应当避免使用#if, 因为该宏的值可能就是被定义为0. 而应当使用下面介绍的#ifdef或#ifndef.

注意: #if, #elif, #else之后的宏只能是对象宏. 如果name为名的宏未定义, 或者该宏是函数宏. 那么在gcc中使用"-Wundef"选项会显示宏未定义的警告信息.

4, #ifdef, #ifndef, defined.

#ifdef, #ifndef, defined用来测试某个宏是否被定义

#ifdef name  或 #ifndef name

它们经常用于避免头文件的重复引用:

#ifndef __FILE_H__

#define __FILE_H__

#include "file.h"

#endif

defined(name): 若宏被定义,则返回1, 否则返回0.

它与#if, #elif, #else结合使用来判断宏是否被定义, 乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef. defined用于在一条判断语句中声明多个判别条件:

#if defined(VAX) && defined(UNIX) && !defined(DEBUG)

和#if, #elif, #else不同, #indef, #ifndef, defined测试的宏可以是对象宏, 也可以是函数宏. 在gcc中使用"-Wundef"选项不会显示宏未定义的警告信息.

5, #include , #include_next

#include用于文件包含. 在#include 命令所在的行不能含有除注释和空白符之外的其他任何内容.

#include "headfile"

#include <headfile>

#include 预处理标记

前面两种形式大家都很熟悉, "#include 预处理标记"中, 预处理标记会被预处理器进行替换, 替换的结果必须符合前两种形式中的某一种.

实际上, 真正被添加的头文件并不一定就是#include中所指定的文件. #include"headfile"包含的头文件当然是同一个文件, 但#include <headfile>包包含的"系统头文件"可能是另外的文件. 但这不值得被注意. 感兴趣的话可以查看宏扩展后到底引入了哪些系统头文件.

关于#include "headfile"和#include <headfile>的区别以及如何在gcc中包含头文件的详细信息, 参考本blog的GCC笔记.

相对于#include, 我们对#include_next不太熟悉. #include_next仅用于特殊的场合. 它被用于头文件中(#include既可用于头文件中, 又可用于.c文件中)来包含其他的头文件. 而且包含头文件的路径比较特殊: 从当前头文件所在目录之后的目录来搜索头文件.

比如: 头文件的搜索路径一次为A,B,C,D,E. #include_next所在的当前头文件位于B目录, 那么#include_next使得预处理器从C,D,E目录来搜索#include_next所指定的头文件.

可参考cpp手册进一步了解#include_next

6, 预定义宏

标准C中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义.

下面列出一些标准C中常见的预定义对象宏(其中也包含gcc自己定义的一些预定义宏:

__LINE__             当前语句所在的行号, 以10进制整数标注.

__FILE__             当前源文件的文件名, 以字符串常量标注.

__DATE__            程序被编译的日期, 以"Mmm dd yyyy"格式的字符串标注.

__TIME__            程序被编译的时间, 以"hh:mm:ss"格式的字符串标注, 该时间由asctime返回.

__STDC__            如果当前编译器符合ISO标准, 那么该宏的值为1

__STDC_VERSION__    如果当前编译器符合C89, 那么它被定义为199409L, 如果符合C99, 那么被定义为199901L.

                    我用gcc, 如果不指定-std=c99, 其他情况都给出__STDC_VERSION__未定义的错误信息, 咋回事呢?

__STDC_HOSTED__        如果当前系统是"本地系统(hosted)", 那么它被定义为1. 本地系统表示当前系统拥有完整的标准C库.

gcc定义的预定义宏:

__OPTMIZE__            如果编译过程中使用了优化, 那么该宏被定义为1.

__OPTMIZE_SIZE__    同上, 但仅在优化是针对代码大小而非速度时才被定义为1.

__VERSION__            显示所用gcc的版本号.

可参考"GCC the complete reference".

要想看到gcc所定义的所有预定义宏, 可以运行: $ cpp -dM /dev/null

7, #line

#line用来修改__LINE__和__FILE__.

e.g.

  printf("line: %d, file: %s\n", __LINE__, __FILE__);

#line 100 "haha"

  printf("line: %d, file: %s\n", __LINE__, __FILE__);

  printf("line: %d, file: %s\n", __LINE__, __FILE__);

显示:

line: 34, file: 1.c

line: 100, file: haha

line: 101, file: haha

8, #pragma, _Pragma

#pragma用编译器用来添加新的预处理功能或者显示一些编译信息. #pragma的格式是各编译器特定的, gcc的如下:

#pragma GCC name token(s)

#pragma之后有两个部分: GCC和特定的pragma name. 下面分别介绍gcc中常用的.

(1) #pragma GCC dependency

dependency测试当前文件(既该语句所在的程序代码)与指定文件(既#pragma语句最后列出的文件)的时间戳. 如果指定文件比当前文件新, 则给出警告信息.

e.g.

在demo.c中给出这样一句:

#pragma GCC dependency "temp-file"

然后在demo.c所在的目录新建一个更新的文件: $ touch temp-file, 编译: $ gcc demo.c 会给出这样的警告信息:  warning: current file is older than temp-file

如果当前文件比指定的文件新, 则不给出任何警告信息.

还可以在在#pragma中给添加自定义的警告信息.

e.g.

#pragma GCC dependency "temp-file" "demo.c needs to be updated!"

1.c:27:38: warning: extra tokens at end of #pragma directive

1.c:27:38: warning: current file is older than temp-file

注意: 后面新增的警告信息要用""引用起来, 否则gcc将给出警告信息.

(2) #pragma GCC poison token(s)

若源代码中出现了#pragma中给出的token(s), 则编译时显示警告信息. 它一般用于在调用你不想使用的函数时候给出出错信息.

e.g.

#pragma GCC poison scanf

scanf("%d", &a);

warning: extra tokens at end of #pragma directive

error: attempt to use poisoned "scanf"

注意, 如果调用了poison中给出的标记, 那么编译器会给出的是出错信息. 关于第一条警告, 我还不知道怎么避免, 用""将token(s)引用起来也不行.

(3) #pragma GCC system_header

从#pragma GCC system_header直到文件结束之间的代码会被编译器视为系统头文件之中的代码. 系统头文件中的代码往往不能完全遵循C标准, 所以头文件之中的警告信息往往不显示. (除非用 #warning显式指明).

(这条#pragma语句还没发现用什么大的用处)

由于#pragma不能用于宏扩展, 所以gcc还提供了_Pragma:

e.g.

#define PRAGMA_DEP #pragma GCC dependency "temp-file"

由于预处理之进行一次宏扩展, 采用上面的方法会在编译时引发错误, 要将#pragma语句定义成一个宏扩展, 应该使用下面的_Pragma语句:

#define PRAGMA_DEP _Pragma("GCC dependency \"temp-file\"")

注意, ()中包含的""引用之前引该加上\转义字符.

9, #, ##

#和##用于对字符串的预处理操作, 所以他们也经常用于printf, puts之类的字符串显示函数中.

#用于在宏扩展之后将tokens转换为以tokens为内容的字符串常量.

e.g.

#define TEST(a,b) printf( #a "<" #b "=%d\n", (a)<(b));

注意: #只针对紧随其后的token有效!

##用于将它前后的两个token组合在一起转换成以这两个token为内容的字符串常量. 注意##前后必须要有token.

e.g.

#define TYPE(type, n) type n

之后调用: 

TYPE(int, a) = 1;

TYPE(long, b) = 1999;

将被替换为:

int a = 1;

long b = 1999;

(10) #warning, #error

#warning, #error分别用于在编译时显示警告和错误信息, 格式如下:

#warning tokens

#error tokens

e.g.

#warning "some warning"

注意, #error和#warning后的token要用""引用起来!

(在gcc中, 如果给出了warning, 编译继续进行, 但若给出了error, 则编译停止. 若在命令行中指定了 -Werror, 即使只有警告信息, 也不编译.

————————————————————————————————————————————

预编译指令以“#”开始,且一般都在一行的顶格写起(不是必须的),行尾不需要分号。
    预编译指令为文件作用域,即其作用范围仅在其所在的文件中(头文件中的预编译指令将在所有包含该头文件的文件中有效)

1、#include:将某个文件的内容读进来。
    有两种形式:#include <filename.h>与#include "filename.h",
    前者表示filename.h为标准头文件,预编译器将在预定义一些位置寻找该头文件;后者表示filename.h为用户自定义的头文件,预编译器将在包含该指令的文件所在的位置开始查找该头文件。

2、#ifdef    #ifndef    #define    #endif
    #ifdef DEBUG   // 测试DEBUG是否已经定义
    #ifndef DEBUG // 测试DEBUG是否没有定义
    #define             // 定义DEBUG
    #endif                // #ifdef或者#ifndef语句结束

    C++中,定义头文件时,为了避免头文件被多次引用而造成的重复定义,需要将整个头文件内容包含在下面的预编译指令中(假设头文件为file.h):
    #ifndef FILE_H
    #define FILE_H
    // 头文件的内容
    #endif

3、常见的预编译常量
    __plusplus     // 编译C++程序时编译器自动定义
    __STDC__    // 编译C程序时编译器自动定义
    __FILE__      // 当前编译的文件名
    __LINE__      // 当前编译文件的当前行
    __DATE__    // 编译文件时的日期
    __TIME__     // 编译文件时的时间
  
    其中,__FILE__与__LINE__在编译期间是不断更新的,其他四个变量在编译期间固定。

4、assert()
    C语言定义的预编译宏,断言某个表达式为true;若为false,则抛出异常。
————————————————————————————————————————

许多初学 VC 的朋友也许都为那么一个问题困扰过:

    为什么所有的 cpp 都必须 #include "stdafx.h"

    也许请教了别的高手之后,他们会告诉你,这是预编译头,必须包含。可是,这到底
是为什么呢?预编译头有什么用呢?

    这得从头文件的编译原理讲起。其实头文件并不神秘,它的全部作用,就是把自己的
所有内容直接“粘贴”到相应的 #include 语句处。如果不相信的话,不妨做个实验,将
一个 cpp 中的所有 #include 语句删掉,并将它包含的文件粘贴到相应的位置,你会发
现,文件的编译和运行都完全没有受到影响。其实,编译器在编译你的程序的时候,所做
的第一件事,也就是展开所有的 #include 语句和 #define 语句。

    头文件的出现,固然给书写程序带来了很大方便。可是到了 Windows 时代后,慢慢
就呈现出一些问题了。几乎所有的 Windows 程序都必须包含 windows.h,而那个文件却
硕大无比,将它展开后往所有文件中一粘贴,编译的时候立刻慢得像只蜗牛。

    到了 MFC 时代后,情况更为恶劣了。毕竟 C 风格的 Windows 头文件里面包含的还
仅仅是函数定义和宏,编译难度不算太大,而 MFC 库里面的头文件可都是类声明啊!更
何况,一个最简单的工程,都会生成大量的类,需要用到大量的函数。如果工程稍微复杂
一些,编译难度可想而知!

    但是,人们惊奇地发现,虽然用到的头文件又多又杂,但是在一个工程中,总有那么
一堆头文件,是几乎所有 cpp 都必须包含的。那么,可不可以把这些头文件提取出来,
只编译一编,然后所有其它 cpp 就都能使用呢?没错,这就是预编译头的思想都由来!

    实践证明,使用了预编译头技术后,编译速度大大提高了。可以到你的工程目录下的
Debug 或 Release 目录中看一看,里面有一个体积极为硕大的 .pch 文件,那就是传说
中的“编译之后的预编译头”。

    使用了预编译头技术后,虽然带来了极大地方便,但也造成了一个问题:由于它假定
预编译头中包含过的头文件会在所有 cpp 中使用,因此它在编译你的 cpp 的时候,就会
将预编译头中已经编译完的部分加载到内存中。如果它突然发现你的 cpp 居然没有包含
预编译头,它就会很郁闷,因为它不知道该如何将已编译完的部分从内存中请出去,整个
编译过程就会失败。

    因此,如果你使用了预编译头技术,就必须在所有的 cpp 中包含预编译头。MFC 工
程中为你建立了一个默认的预编译头 stdafx.h,如果你愿意,也可以在自己的工程中使
用其它文件名作为你的预编译头,如果你觉得有必要。
 

预编译头文件的使用 
关键字:预编译,/Yu,/Yc,/Yx
本文介绍VC6的预编译功能的使用,由于预编译详细使用比较的复杂,这里只介绍几个最重要的预编译指令: /Yu, /Yc,/Yx,/Fp。其它的详细资料可以参考:
      MSDN->Visual Studio D6.0Document -> Visual C++6.0 Document
         ->VC++ Programmer Guider ->Compiler and Linker
         ->Details->Creating Precompiled Header files

预编译头的概念:
所谓的预编译头就是把一个工程中的那一部分代码,预先编译好放在一个文件里(通常是以.pch为扩展名的),这个文件就称为预编译头文件这些预先编译好的代码可以是任何的C/C++代码--------甚至是 inline的函数,但是必须是稳定的,在工程开发的过程中不会被经常改变。如果这些代码被修改,则需要重新编译生成预编译头文件。注意生成预编译头文件是很耗时间的。同时你得注意预编译头文件通常很大,通常有6-7M大。注意及时清理那些没有用的预编译头文件。
也许你会问:现在的编译器都有 Time stamp的功能,编译器在编译整个工程的时候,它只会编译那些经过修改的文件,而不会去编译那些从上次编译过,到现在没有被修改过的文件。那么为什么还要预编译头文件呢?答案在这里,我们知道编译器是以文件为单位编译的,一个文件经过修改后,会重新编译整个文件,当然在这个文件里包含的所有头文件中的东西(.eg Macro, Preprocessor )都要重新处理一遍。VC的预编译头文件保存的正是这部分信息。以避免每次都要重新处理这些头文件。
预编译头的作用:
方法一:手动方法
根据上文介绍,预编译头文件的作用当然就是提高便宜速度了,有了它你没有必要每次都编译那些不需要经常改变的代码。编译性能当然就提高了。
预编译头的使用:
     要使用预编译头,我们必须指定一个头文件,这个头文件包含我们不会经常改变的代码和其他的头文件,然后我们用这个头文件来生成一个预编译头文件(.pch文件)
 想必大家都知道 StdAfx.h这个文件。很多人都认为这是VC提供的一个“系统级别”的,编译器带的一个头文件。其实不是的,这个文件可以是任何名字的。我们来考察一个典型的由AppWizard生成的MFC Dialog Based 程序的预编译头文件。(因为AppWizard会为我们指定好如何使用预编译头文件,默认的是StdAfx.h,这是VC起的名字)。我们会发现这个头文件里包含了以下的头文件:
#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的必须包含的头文件,当然我们不太可能在我们的工程中修改这些头文件的,所以说他们是稳定的。
那么我们如何指定它来生成预编译头文件。我们知道一个头文件是不能编译的。所以我们还需要一个cpp文件来生成.pch 文件。这个文件默认的就是StdAfx.cpp。在这个文件里只有一句代码就是:#include “Stdafx.h”。原因是理所当然的,我们仅仅是要它能够编译而已―――也就是说,要的只是它的.cpp的扩展名。我们可以用/Yc编译开关来指定 StdAfx.cpp来生成一个.pch文件,通过/Fp编译开关来指定生成的pch文件的名字。打开project ->Setting->C/C++ 对话框。把Category指向Precompiled Header。在左边的树形视图里选择整个工程 (如图)
(图1)
在图中我们的Project Options(右下角的那个白的地方)可以看到 /Fp “debug/PCH.pch”,这就是指定生成的.pch文件的名字,默认的通常是 <工程名>.pch(我的示例工程名就是PCH)。
然后,在左边的树形视图里选择StdAfx.cpp.如图:(图2)
这时原来的Project Option变成了 Source File Option(原来是工程,现在是一个文件,当然变了)。在这里我们可以看到 /Yc开关,/Yc的作用就是指定这个文件来创建一个Pch文件。/Yc后面的文件名是那个包含了稳定代码的头文件,一个工程里只能有一个文件的可以有 YC开关。VC就根据这个选项把 StdAfx.cpp编译成一个Obj文件和一个PCH文件。
   然后我们再选择一个其它的文件来看看,如图:
在这里,Precomplier 选择了 Use ………一项,头文件是我们指定创建PCH 文件的stdafx.h
文件。事实上,这里是使用工程里的设置,(如图1)/Yu”stdafx.h”。
   这样,我们就设置好了预编译头文件。也就是说,我们可以使用预编译头功能了。以下是注意事项:
1): 如果使用了/Yu,就是说使用了预编译,我们在每个.cpp文件的最开头,我强调一遍是最开头,包含 你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如果你没有包含这个文件,就告诉你Unexpected file end. 如果你不是在最开头包含的,你自己试以下就知道了,绝对有很惊人的效果…..
2)如果你把pch文件不小心丢了,根据以上的分析,你只要让编译器生成一个pch文件就可以了。也就是说把 stdafx.cpp(即指定/Yc的那个cpp文件)从新编译一遍就可以了。当然你可以傻傻的 Rebuild all。简单一点就是选择那个cpp文件,按一下Ctrl + F7就可以了。
方法二。自动使用
很简单只要指定/YX就可以了。或者在上图中选择Automatic………就可以了。注意的事情是如果你指定了/Yc /Yu的话,/Yx是会被忽略的。前者的优先级别高一些.

原文出处: http://fengqing888.blog.163.com/blog/static/330114162009102011531927/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值