原文: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; \
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; \
typeof (a) _a = (a); \
typeof (b) _b = (b); \
(_a < _b) ? _a : _b; \
} while (0)
这里有三点需要注意:
(i)为了防止参数的多次取值,我们利用临时变量_a, _b来存储了a,b的取值结果,这样就避免了多次取值
(ii)由于宏定义只执行字符替换,为了为临时变量声明一个合适的类型,因此使用了类型声明, typeof
typeof的介绍参见文档
http://gcc.gnu.org/onlinedocs/gcc/Typeof.html
http://gcc.gnu.org/onlinedocs/gcc/Typeof.html
(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)
#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 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(); }