宏定义的作用及使用方法

原文: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
      typeof的介绍参见文档  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)
#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(); }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值