预处理中的伪指令通常以#开头,主要包括:宏定义、条件编译、头文件包含和其他符号。在复杂的设备中,为避免运行时的冲突,多个模块程序要合理地使用预处理程序。其中头文件包含如#include "FileName.h"(自定义文件)或#include <FileName.h>(系统头文件),开发人员在定义头文件时包含这些文件要用(""),且需要标明路径,通常用相对路径或在Makefile文件中定义好,否则编译器找不到该文件会出错返回。
宏定义程序中用一个标识符来表示特定字符串或数字,称为“宏”。宏定义有两种:有参数的宏定义(简称“带参宏定义”)和无参数的宏定义(简称“无参宏定义”)。下面分别讨论注意事项。
1.无参宏定义
(1)宏名在源程序中若用双引号括起来,则不视为一个宏而视为一个字符串,预处理程序不作宏替换,举例如下。
#define NOT 0 main () { printf ("NOT\n"); }
上例中定义宏名NOT表示0,但在printf语句中NOT被看成字符串处理。
(2)宏定义作为一种简单的代换,预处理程序并不作任何检查,但会在编译阶段检查,因此定义宏时需要考虑一些发生异常情形时的处理方法。
(3)作用域。在函数之外的宏定义,作用域为宏初始定义起到本文件的源程序结束。终止作用域可使用#undef命令,当被多个文件包含时,作用域就在所引用的文件中。
(4)宏定义嵌套。在宏定义中可以使用前文已定义的宏,由预处理程序进行嵌套层代换。
#define PIE 3.14 #define S PIE*y*y
调用printf ("%f", S);
在宏替换后变为:printf ("%f",3.14*y*y);
(5)宏定义与typedef的区别
宏定义是简单的字符串替换,在预处理阶段完成;typedef 是在编译时处理的,不是进行简单的代换,而是对类型说明符重新命名。新名称代表和原类型说明有同样的功能。
#define INT32 int* typedef (int*) INT;
这两者在形式上相似,在实际使用中却不相同。
INT32x,y;在宏代换后变成:int *x,y;这里x是指向整型的指针变量,而y是整型变量。
INTx,y;这里x,y都是指向整型的指针变量。
2.带参宏定义
(1)使用空格时要恰当。如定义较大数的宏#define MAX (a,b) (a>b)?a:b就不能写成如下形式。
#define MAX (a,b) (a>b)?a:b
上例中,预处理器认为MAX是无参宏定义。
(2)宏定义中类型定义要注意匹配。因为形式参数不分配内存单元,宏调用中的实际参数(以下简称实参)有具体的值,不是参数传递而是符号替换。
(3)字符串内的形式参数(以下简称形参)通常要用括号括起来,以避免出错。
#define SEQU (y) y*y main () { int a,sequ; scanf ("%d",&a); sequ =SEQU (a+1); printf ("sequ =%d\n", sequ); }
上例中没有使用括号,如果用下例中的两个宏定义,运行结果有很大差异,读者可以自行测试一下。
#define SEQU (y) (y)* (y) #define SEQU (y) ((y)* (y))
(4)带参宏和带参函数形式相似,即使两者的运行结果相同,但本质仍然不同。同一表达式用函数调用会涉及参数入栈/出栈、分配内存等,耗费的时间也不一样。所以常用的做法是将简短的函数(如10行以内)用宏或者内联函数来替代。
(5)宏定义可用来定义多个语句,类似于函数功能,在调用宏时,这些语句可替换到源程序中按顺序运行。
#define COMPUTE (a,b,c,d) a=l*w;b=l*h;c=w*h;d=w*l*h; main () { int l=3,w=4,h=5,a,b,c, d; COMPUTE (a,b,c,d); printf ("a=%d\nb=%d\nc=%d\nd=%d\n",a,b,c,d); }
上例中用宏名COMPUTE表示4个赋值语句。在宏调用时,把4个语句展开并用实际参数替代形式参数,将计算结果送入实际参数中。
#define LIST_FIND(head,cmpfn,type,args) \ ({ \ const struct list_head*__i=(head); \ do{ \ __i=__i->next; \ if(__i==(head)){ \ __i=NULL; \ break; \ } \ }while(!cmpfn((const type)__i,args)); \ (type)__i; \ })