宏定义的作用及使用方法

  • Post author:
  • Post category:其他


原文:http://blog.chinaunix.net/uid-24830931-id-2945760.html

宏广泛用于C语言程序中,本文总结了宏的分类, 作用与使用注意事项
宏定义分类:
(1)不带参数的宏定义
形式: #define 宏名 [宏体]
功能:可以实现用宏体代替宏名
使用实例: #define TRUE 1
作用:程序中多次使用TRUE,如果需要对TRUE的值进行修改,只需改动一处就可以了
(2)带参数的宏: #define 宏名 ( 参数表) [宏体]
宏定义作用:
(1)方便程序的修改
上面的#define TRUE 1就是一个实例
(2)提高程序的运行效率
宏定义的展开是在程序的预处理阶段完成的,无需运行时分配内存,能够部分实现函数的功能,却没有函数调用的压栈、弹栈开销,效率较高


(3)增强可读性
这点不言而喻,当我们看到类似PI这样的宏定义时,自然可以想到它对应的是圆周率常量
(4)字符串拼接
例如:
#define CAT(a,b,c) a##b##c
main()
{
printf(“%d\n” CAT(1,2,3));
printf(“%s\n”, CAT(‘a’, ‘b’, ‘c’);
}
程序的输出会是:
123
abc
(5)参数转化成字符串
示例:
#defind CAT(n) “abc”#n
main()
{
printf(“%s\n”, CAT(15));
}
输出的结果会是
abc15
(6)用于程序调试跟踪

常见的用于调试的宏有,_ L I N E _,_ F I L E _,_ D A T E _,_ T I M E _,_ S T D C _

(7)实现可变宏

举例来说:

#define PR(…) printf(_ _VA_ARGS_ _)

使用宏定义中常见的注意事项有:
(1)不要为宏定义加分号

宏定义在预处理阶段只是进行字符串替换
例如: #define PI 3.14159
float square = PI*r*r;
如果此时将PI加上分号,显然会编译出错
(2)为了防止出现意想不到的替换结果,对于带参数的宏,最好对每个参数和宏都加上配对的括号
例如: #define MUL(a,b) a*b
此时如果这样来使用 a=MUL(2+3, 4+5);
替换后的结果是a=2+3*4+5。结果变成了19,而不是我们期望的45
为了解决这个问题,我们为改进一下
#define MUL(a,b) (a)*(b)
上面的例子没有问题了,但还有下面的情况处理不了
#define SUB(a,b) (a)-(b)
当我们调用 int x = 10*SUB(8,2)
展开后的结果是x=10*(8)-2=72,而不是我们所期望的60
因此比较好的写法是
#define SUB(a,b) ((a)-(b))
(3)防止参数多次取值的错误
这个错误比较隐蔽一些,通过例子来说明
#define MIN(a,b) ((a) > (b) ? (a) : (b))
如果我们这样来使用
int x=1;
int y=2;
int z=MIN(x++,y++);
int k=MIN(y++,x++);
z展开后是 int z=((x++) > (y++) ? (x++) : (y++));
k展开后是 int z=((y++) > (x++) ? (y++) : (x++));
MIN宏设计的初衷是希望得到x,y的较小值,z和k都应该返回1
但结果却是z=2,k=3
同样,如果宏的一个参数是一个函数,也会出现函数被多次执行的情况
比较直观的想法是使用括号将代码段括起来
#define MIN(a,b) \
{        \


typeof (a) _a = (a); \


typeof (b) _b = (b); \


(_a < _b) ? _a : _b; \
}
但这样的定义,当我们按照常规这样来使用的话
z=MIN(x++,y++);
展开后变成了z={typeof (x++) _x = (x++); typeof (y++) _y = (y++); (_a < _b) ? _a : _b;};
结尾的”;”还是会导致编译错误
解决这个问题的比较理想的做法是:
#define MIN(a,b) \
do {        \


typeof (a) _a = (a); \


typeof (b) _b = (b); \


(_a < _b) ? _a : _b; \
} while (0)
这里有三点需要注意:
(i)为了防止参数的多次取值,我们利用临时变量_a, _b来存储了a,b的取值结果,这样就避免了多次取值
(ii)由于宏定义只执行字符替换,为了为临时变量声明一个合适的类型,因此使用了类型声明, typeof
(iii)为了防止宏使用结尾的分号导致错误,尽量使用do while(0)来讲代码块包含起来
看来,写好一个宏真是不容易。
p.s.在看LVS源码时,能看到很多这种定义,这下总算一释心中疑惑了。
#define IP_VS_BUG() BUG()


#define IP_VS_ERR_RL(msg, …)      \


do {        \


if (net_ratelimit())     \


pr_err(msg, ##__VA_ARGS__);   \


} while (0)
#ifdef CONFIG_IP_VS_DEBUG


#define EnterFunction(level)      \


do {        \


if (level <= ip_vs_get_debug_level())   \


printk(KERN_DEBUG    \


pr_fmt(“Enter: %s, %s line %i\n”), \


__func__, __FILE__, __LINE__);  \


} while (0)


#define LeaveFunction(level)      \


do {        \


if (level <= ip_vs_get_debug_level())   \


printk(KERN_DEBUG    \


pr_fmt(“Leave: %s, %s line %i\n”), \


__func__, __FILE__, __LINE__);  \


} while (0)


#else


#define EnterFunction(level)   do {} while (0)


#define LeaveFunction(level)   do {} while (0)


#endif
#define IP_VS_WAIT_WHILE(expr) while (expr) { cpu_relax(); }