所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所做的工作。预处理是C语言的一个重要功能,它由预处理程序负责完成的。对当一个源文件进行编译时,系统将自动引用预处理程序中的预处理部分进行处理,处理完毕自动进入对源程序的编译。
C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等,合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块程序设计。
宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的,在C语言中,宏分为有参数和无参数两种。
1、无参宏定义
无参宏的宏名后不带参数,其定义的一般形式为
#define 标识符 字符串
#表示这是一条预处理命令,凡是以#开头的均为预处理命令;define为宏定义命令;标识符为所定义宏名;字符串可以是常数、表达式、格式串等。
常对程序中反复使用的表达式进行宏定义。
说明:1)宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名。
2)宏定义不是说明语句,在行末不必加分号,如加上分号则连分号也一起置换。
3)宏定义必须写在函数外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用”#undef“命令
2、带参宏定义
在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。一般形式为:
#define 宏名(形参表) 字符串
在字符串中含有各个形参。带参宏调用的一般形式为
宏名(实参表);
带参宏定义与自定义函数的区别
1)带参宏定义中,形式参数不分配内存单元 ,因此不必做类型定义;而宏调用中的实参有具体的值,要用它们去代换形参,因此必须作类型说明。这与函数中的情况是不同的,在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行”值传递“。而在带参宏中,只是符号代换,不存在值传递的问题。
2)在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。
注意:在宏定义中,字符串内的形参通常要用括号括起来以避免出错。
写好C语言,漂亮的宏定义很重要,使用宏定义可以防止出错,提高可移植性、可读性、方便性等。
1、防止一个头文件被重复包含
#ifndef COMDEF_H
#define COMDEF_H
//头文件内容
#endif
2、得到指定地址上的一个字节或字
#define MEM_B (x) (*( (byte *) (x) ) )
#define MEM_W (x) (*( (word *) (x) ) )
3、求最大值和最小值
#define MAX (x,y) ( ( (x) > (y) ) ? (x) : (y) )
#define MIN (x,y) ( ( (x) > (y) ) ? (x) : (y) )
4、得到一个field在结构体(struct)中的偏移量
#define FPOS (type,field) \
/*lint -e545 */ ( (dword) & ( ( type *) 0)->field) /*lint +e545*/
5、得到一个结构体中field所占用的字节数
#define FSIZ( type,field ) sizeof ( ( ( type *) 0)->field )
6、按照LSB格式把两个字节转化为一个Word
#define FLIPW( ray ) ( ( ( ( word) (ray)[0] ) *256 ) + (ray)[1] )
7、按照LSB格式把一个Word转化为两个字节
#define flopw (ray,val ) \(ray)[0] = ( (val) /256 );\
(ray)[1] = ( (val) & 0xff)
8、得到一个变量的地址 (word宽度)
#define B_PTR( var ) ( (byte *) (void *) &(var) )
#define W_PTR( var ) ( (word *) (void * ) & (var) )
9、得到一个字的高位和低位字节
#define WORD_LO(***) ( (byte) ( (word) (***) & 255 ) )
#define WORD_HI(***) ( (byte) ( (word) (***) >> 8 ) )
10、防止溢出的一个方法
#define INC_SAT(val) (val = ( (val)+1 > (val) ) ? (val) + 1:(val) )
11、对于IO空间映射在存储空间的结构,输入输出处理
#define inp(port) (*( (volatile byte * ) (port) ) )
#define inpw(port) (*( (volatile word * ) (port) ) )
#define inpdw(port) (*( (volatile dword * ) (port) ) )
#define outp (port,val) (*( (volatile byte * ) (port) ) = ( (byte) (val) ) )
#define outpw (port,val) (*( (volatile word * ) (port) ) = ( (word) (val) ) )
12、使用一些宏跟踪调试
ANSI标准说明了五个预定义的宏名,他们分别是_LINE_、_FILE_、_DATE_、_TIME_、_STDC_。如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持,记住编译程序也许还提供其他预定义的宏名。
_LINE_及_FILE_宏指令打印所在行数和函数名。_DATE_宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。源代码翻译到目标代码的时间作为串包含在_TIME_中,串形式为“时:分:秒”。如果实现是标准的,则宏_STDC_含有十进制常量1;如果它含有任何其他数,则实现是非标准的。可以定义宏,例如,当定义了_DEBUG,输出数据信息和所在文件所在行
#ifdef _DEBUG
#define DEBUGMSG(msg,date) printf(msg);
printf ("%d%d%d",date,_LINE_,_FILE_)
#else
#define DEBUGMSG(msg,date)
#endif