C语言深度解剖——读书笔记-5、预处理

来源:互联网 发布:国鑫金服行情软件 编辑:程序博客网 时间:2024/06/18 11:54
第三章    预处理
章节目录
1,宏定义               2,条件编译                    3,文件包含                  4,#error预处理
5,#line预处理      6,#pragma预处理         7,#运算符                    8,##运算符


3.0  预处理指令

预处理名称

意义

#define

宏定义

#undef

撤销已定义的宏名

#include

使编译程序将另一源文件嵌入到带有#include 的源文件中

#if

#if 的一般含义是:如果#if后面的常量表达式为true ,则编译它与#endif之间的代码,

否则跳过这些代码

#else

#elif

#endif

#ifdef

用#ifdef与#ifndef命令分别表示“如果有定义”与“如果无定义”,

是条件编译的另一种方法

#ifndef

#line

改变当前行数和文件名称

#error

编译程序时,只要遇到#error 就会生成一个编译错误提示消息,并停止编译

#pragma

为实现时定义的命令,它允许向编译程序传送各种指令

另外,ANSI标准C还定义了如下几个宏:
__LINE__表示正在编译的文件行号。
__FILE__表示正在编译的文件的名字。
__DATE__表示编译时刻的日期字符串,例如:“25 Dec 2007”。
__TIME__表示编译时刻的时间字符串,例如:“12:30:55”。
__STDC__判断该文件是不是定义成标准C程序。
【注意】如果编译器不是标准的,则可能仅支持以上宏的一部分,或根本不支持。当然编译器也有可能还提供其它预定义的宏名。宏名的书写由标识符与两边各二条下划线构成。


编译过程简介:


 预编译:

a.处理所有的注释,以空格代替。

b.将所以#define删除,并展开所有的宏定义,字符串替换。

c.处理条件编译指令#if,#ifdef,#elif,#else,#endif

d.处理#include,并展开被包含的文件,把头文件中的声明,全部拷贝到文件中。

e.保留编译器需要使用的#pragma指令、

怎么样观察这些变化呢?最好的方法就是在GCC中,输入预处理指令,可以看看不同文件经过预处理后变成什么样了,

预处理指令:gcc -E file.c -o file.i   注意:-C -E一起使用是预编译的时候保留注释。

   编译:

a.对预处理文件进行一系列词法分析,语法分析和语义分析

                词法分析:主要分析关键字,标示符,立即数等是否合法

                语法分析:主要分析表达式是否遵循语法规则

                语义分析:在语法分析的基础上进一步分析表达式是否合法

b.分析结束后进行代码优化生成相应的汇编代码文件               编译指令:gcc -S  file.c  -o  file.s

   汇编:

汇编器将汇编代码转变为机器可以执行的指令,每个汇编语句几乎都对应一条机器指令,其实机器指令就是机器码,就是2进制码。汇编指令:gcc  -c  file.c  -o file.o  注意:-c是编译汇编不连接。

   链接:

再把产生的.o文件,进行链接就可以生成可执行文件。连接指令:gcc  file.o  file1.o  -o  file  这句指令是链接file.o和file1.o两个编译并汇编的文件,并生成可执行文件file。

链接分两种:静态链接和动态链接,静态链接是在编译器完成的,动态链接是在运行期完成的。静态链接的指令是:gcc -static file.c -o file对于一些没有动态库的嵌入式系统,这是常用的。

一般要想通过一条指令生成可执行文件的指令是:   gcc file.c  -o  file

   资料:这里面说到了很多关于gcc的使用的问题,我提资源下载地址http://download.csdn.net/detail/qq418674358/6041183   



3.1    宏定义

【问题1】数值宏常量,例如:#define PI  3.14
在写程序的时候,尽量不要使用“魔数”。
const修饰的数据是有类型的,而define宏定义的数据没有类型。为了安全,建议在定义一些宏常数的时候用const代替,编译器会给const修饰的只读变量做类型校验,减少错误的可能。但注意:const修饰的不是常量而是readonly的变量,const修饰的只读变量不能用来作为定义数组的维数,也不能放在case关键字后面。(在VS2008测试下,发现可以呀?)
【问题2】字符串宏常量
#define ENG_PATH_1 "C://windows" (在定义路径的时候常用)
【问题3】用define宏定义注释符号正确吗?(错误)

 

  1. #define BSC //  
  2. #define  BMC /*  
  3. #define EMC */  
  4. BSC this is my single-line comment  
  5. BMC this is my multi-line comment EMC  

上面是不正确的。因为注释先于预处理指令被处理。
【问题4】用define宏定义表达式
记住:define是个演技高超的替身演员,但也经常耍大牌。要搞定它其实很简单,别吝啬使用括号就行了。
【问题5】宏定义中的空格
#define SUM   (x)   (x)+(x)   // error
编译器认为这是定义一个宏:SUM,其代表的是(x)   (x)+(x)   。
关键问题是在于,SUM后面的这个空格。一定要知道什么时候该用空格,什么时候不该用空格。这个空格仅仅在定义的时候有效,在使用这个宏函数的时候,空格会被编译器忽略掉。
【问题6】#undef
#undef是用来撤销宏定义的。

  1. #define PI 3.14  
  2. // some codes  
  3. #undef PI  
  4. // the below codes can not use macro IP again  

【思考】变量z等于多少?

  1. #define X 3  
  2. #define  Y X*2  
  3. #undef X  
  4. #define  X 2  
  5. int z=Y;// 4  

 

3.2    条件编译

条件编译的功能使得我们按不同条件去编译不同程序部分,因而产生不同的目标代码文件。这对程序的移植和调试很有用的。

条件编译有3种形式:

(1)第一种形式:

#ifdef  标识符

程序段1

#else

程序段2

#endif

上面这种形式的功能是:如果标识符已被#define命令定义过,则对程序段1进行编译;否则对程序段2进行编译。

(2)第二种形式:

#ifndef  标识符

程序段1

#else

程序段2

#endif

这种形式的功能是:如果标识符未被#define命令定义过,则对程序段1进行编译;否则对程序段2进行编译;

(3)第三种形式:

# if 常量表达式

程序段1 

#else

程序段2

#endif

这种形式的功能是:如果常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下完成不同的功能;



3.3    文件包含

文件包含是预处理的一个重要的功能,它将多个源文件链接成一个源文件进行,结果将生成一个目标文件。C语言提供#include 命令来实现文件包含的操作,它实际是宏替换的延伸,有两种格式。

1.)格式1:

#include <filename>

其中,filename为要包含的文件名称,用尖括号括起来,也称为头文件,表示预处理到系统规定的路径中去获得这个文件(即C编程系统提供的并存放在指定的子目录下的头文件)。

2.)格式2:

#include "filename"

其中,filename为要包含的文件名称,双引号表示预处理应在当前目录中查找文件名为filename的文件;若没有找到,则按系统指定的路径信息搜索其他目录。找到文件后,用文件内容替换该语句。

#include “” 和<> 的区别 这是很多校园招聘中笔试题都会出现的: 

<> 表示:预处理到系统规定的路径中去获得这个文件

“”双引号表示:预处理应在当前目录中查找文件名为filename的文件
【注意】#include支持相对路径。.代表当前目录,..代表上层目录。













0 0
原创粉丝点击