您的位置:首页 > 编程语言 > C语言/C++

C语言宏定义作用、使用方法小结

2015-10-10 09:51 411 查看
宏广泛用于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(); }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: