Dissection C Chapter 3

来源:互联网 发布:ct断层影像重建算法 编辑:程序博客网 时间:2024/04/28 03:45

本文总结C语言中预处理的一些用法。
2010-10-21   wcdj
本文接:Dissection C Chapter 2
http://blog.csdn.net/delphiwcdj/archive/2010/10/20/5953361.aspx

1,宏定义
2,条件编译
3,文件包含
4,#error预处理
5,#line预处理
6,#pragma预处理
7,#运算符
8,##运算符



【先看几个问题】
[1] 预处理是C语言的一部分吗?
[2] 包含"#"号的都是预处理吗?
[3] 预处理指令后面都不需要加";"吗?

 

                                           ANSI标准定义的C语言预处理指令

 

预处理名称

意义

#define

宏定义

#undef

撤销已定义的宏名

#include

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

#if

控制编译哪一部分的代码

#else

#elif

#endif

#ifdef

分别表示“如果定义和“如果没有定义”

#ifndef

#line

改变当前行数和文件名称

#error

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

#pragma

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

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

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

 

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

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

 

2,条件编译
条件编译的功能使得我们可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。常用的几种形式有:
[1]
#ifdef 标识符
code1
#else
code2
#endif
[2]
#ifndef 标识符
code1
#else
code2
#endif
[3]
#if 常量表达式
code1
#else
code2
#endif

3,文件包含
文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。
C语言提供#include命令来实现文件包含的操作,它实际是宏替换的延伸,有两种格式:
[1] #include <filename>
尖括号括起来的文件称为头文件,表示预处理到系统规定的路径中去获得这个文件(即,C编译系统所提供的并存放在指定的子目录下的头文件)。找到该文件后,用文件的内容替换该语句。
[2] #include "filename"
双引号表示预处理应在当前目录中查找文件名为filename的文件,若没有找到,则按系统指定的路径信息,搜索其他目录。找到文件后,用文件内容替换该语句。
【注意】#include支持相对路径。.代表当前目录,..代表上层目录。

4,#error预处理
#error error-message
【注意】宏串error-message不用双引号包围。

5,#line预处理
#line number["filename"]
#line的作用是改变当前行数和文件名称。(测试发现,在单步调试运行到#line这行时,会跳转到其所指示的那行代码)
【注释】此命令主要用在编译器的编写中。

6,#pragma预处理
在所有的预处理指令中,#pragma指令可能是最复杂的了。它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。
#pragma para
常用的一些参数如下:
[1] #pragma message
它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。(测试时,未发现在调试窗口有信息输出?)
#pragma message("消息文本")
[2] #pragma code_seg
它能够设置程序中函数代码存放的代码段,当开发驱动程序的时候就会使用到它。
[3] #pragma once
比较常用。只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。
[4] #pragma hdrstop
表示预编译头文件到此为止,后面的头文件不进行预编译。
[5] #pragma resource
表示把一些资源文件加入到工程中。
[6] #pragma warning
#pragma warning(disable:4507 34; once:4385; error:164)
等价于:
#pragma warning(disable:4507 34)     // 不显示4507和34号警告信息
#pragma warning(once:4385)        // 4385号警告信息仅报告一次   
#pragma warning(error:164)        // 把164号警告信息作为一个错误                       
[7] #pragma comment
#pragma comment(...)表示将一个注释记录放入一个对象文件或可执行文件中。
#pragma comment(lib, "user32.lib")该指令用来将user32.lib库文件加入到本工程中。
[8] #pragma pack
内存对齐的问题。
#pragma pack(n),编译器将按照n个字节对齐。
#pragma pack(),编译器将取消自定义字节对齐方式。(测试了下,好像不管用?)
在#pragma pack(n)和#pragma pack()之间的代码,按照n个字节对齐。
【注意】成员对齐有一个重要的条件,即每个成员按自己的方式对齐,也就是说虽然指定了按n字节对齐,但并不是所有的成员都是以n字节对齐。
【对齐规则】每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是n字节)中较小的一个对齐,即:min(n, sizeof(item))。并且,结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。对于结构体成员来说,它的默认对齐方式是它的所有成员使用的对齐参数中最大的一个。
例如:

 

7,#运算符
【问题】#也是预处理?(是的)

假如我们希望在字符串中包含宏参数,那我们就可以使用"#",它可以把语言符号转化为字符串。

8,##运算符
##也可以用于宏函数的替换部分,这个运算符把两个语言符号组合成单个语言符号。

【结论】##就是一个粘合剂,将前后两部分粘合起来。

 

 

原创粉丝点击