来源于microchip资深工程师的网络研讨会
关于C的预处理和它对宏定义的预处理,我们来看一些宏的正确和错误的拓展,以及怎样拓展他们,所有的规范都是建立在ANSI C编译器基础上的。
1. 简单的宏处理:
#define name “replacement text”
示例用法:
#define START 20
foobar = START;
“replacement text”可以被其他的宏定义展开
例如:
#define LIMIT 100
#define PREF LIMIT
setting = PREF;
这种宏展开可以拓展至没有宏定义为止
2. 防止重复定义的宏
例如:
#ifndef LCDH
// header file contents go here
// now stop this being included again
#define LCDH
#endif
此种使用的作用范围是从定义到模块结束,或者遇到#undef
3.带参数的宏定义
#define name(argument-list) replacement text
参数会替换掉“replacement text”里的对应项
示例用法:
#define DIFF(a, b) ((a)>=(b) ? (a)-(b) : -1)
result = DIFF(input, 6);
展开后是:
result = ((input)>=(6) ? (input)-(6) : -1);
预处理器能够替换一个或者更多的变量参数,实现更加复杂的替换。为了防止产生非预料的结果,每个替换的变量都要加上括号。
4.字符串替换
例如:
#define PRODUCT(year) "Matrix" #year
const char * productName = PRODUCT(2000);
展开后:
const char * productName = "Matrix" "2000";
最终变成:
const char * productName = "Matrix2000";
当宏定义中有变量参数时,你就能够使用一些特殊的操作符。其中一个就是#操作符,它可以把变量转换成为c字符串。在这个例子里,经过预处理,变量productName将会指向字符串“Matrix2000”,在扩展中,两个符号#year被变量替换,并且产生的字符串和“Matrix”连接。
5.连接变量
例如:
#define BLATCH(bit) LATB##bit
BLATCH(4) = 1;
替换成为:
LATB4 = 1;
另一个宏操作符就是“##”,可以让你连接任意的两个符号,而不仅仅是字符串。在这个例子中,操作符##用来连接符号LATB和bit变量。当产生替换时,它就会扩展成LATB4,这就是PIC处理器的latch B的第四位端口。
这个功能很有用,但是在变量本身是其他需要预处理器扩展的宏时,会有一个潜在的问题,我们看下面:
#define BLATCH(bit) LATB##bit
#define MOTOR 4
BLATCH(MOTOR) = 1;
经过预处理器
会扩展成为:LATBMOTOR = 1;
这显然不是我门想要的结果
问题的根源是,当宏变量进行替换时,宏变量不会被优先扩展。预处理器的定义是,当只有宏变量不在#,##之前或者不在##之后的时候才能被扩展,所以当“MOTOR插入到“BLATCH”中时,他不会被进一步扩展,因为它临近着##操作符。
幸运的时,我们找到了一个方法实现这种扩展:
可以分两步,来进行:
#define PASTE(a, b) a##b
#define BLATCH(bit) PASTE(LATB, bit)
#define MOTOR 4
BLATCH(MOTOR) = 1;
经过预处理器:
LATB4 = 1;
第二个宏实现变量的扩展,第二个宏实现了连接,这就实现了我们要的功能。
我们来看最后一个复杂一些的例子:
#define PASTE2(a,b) a##b
#define PASTE(a,b) PASTE2(a,b)
#ifdef PIC18 // set for PIC18 devices
#define IO LAT
#else
#define IO PORT
#endif
#define PIN 4
#define RDPORT PASTE(PORT, PIN)
#define RDLATCH PASTE(IO, PIN)
#define IOREAD(loc) PASTE(RD, loc)
使用:
result = IOREAD(LATCH);
当我们看懂这个例子的时候,会觉得很有成就感吧,但当你认为这段代码会工作的很顺利时,你将会发现编译器错误或者其他的不良后果。
最后的结果是:result = PASTE(LAT, 4);
并不是我们想要的,究其原因就是每个宏扩展在一个表达式里只能实现一次,具体过程是这样的:
IOREAD(LATCH)
-> PASTE(RD, LATCH)
-> RDLATCH
-> PASTE(IO, PIN)
-> PASTE(LAT, 4); ✘
在进行PASTE第二次扩展时,预处理器不会进行扩展。
解决办法就是另外再写一个,和PASTE同样功能,但是名字不一养的宏来使用。