因为宏只是简单的代码替换,不会像函数一样先将参数计算后,再传递。
示例:如下定义的宏都存在一定的风险
#define RECTANGLE_AREA(a, b) a * b
#define RECTANGLE_AREA(a, b) (a * b)
#define RECTANGLE_AREA(a, b) (a) * (b)
正确的定义应为:
#define RECTANGLE_AREA(a, b) ((a) * (b))
更好的方法是多条语句写成 do while(0)的方式。
示例:看下面的语句,只有宏的第一条表达式被执行。
#define FOO(x) \
printf("arg is %d\n", x); \
do_something_useful(x);
为了说明问题,下面 for 语句的书写稍不符规范
for (blah = 1; blah < 10; blah++)
FOO(blah)
用大括号定义的方式可以解决上面的问题:
#define FOO(x) { \
printf("arg is %s\n", x); \
do_something_useful(x); \
}
但是如果有人这样调用:
if (condition == 1)
FOO(10);
else
FOO(20);
那么这个宏还是不能正常使用,所以必须这样定义才能避免各种问题:
#define FOO(x) do { \
printf("arg is %s\n", x); \
do_something_useful(x); \
} while(0)
用 do-while(0)方式定义宏,完全不用担心使用者如何使用宏,也不用给使用者加什么约束。
示例:如下用法可能导致错误。
#define SQUARE(a) ((a) \* (a))
int a = 5;
int b;
b = SQUARE(a++); // 结果:a = 7,即执行了两次增。
正确的用法是:
b = SQUARE(a);
a++; // 结果:a = 6,即只执行了一次增。
同时也建议即使函数调用,也不要在参数中做变量变化操作,因为可能引用的接口函数,在某个版本升级后,变成了一个兼容老版本所做的一个宏,结果可能不可预知。
说明:使用魔鬼数字的弊端:代码难以理解;如果一个有含义的数字多处使用,一旦需要修改这个数值,代价惨重。
使用明确的物理状态或物理意义的名称能增加信息,并能提供单一的维护点。
解决途径: 对于局部使用的唯一含义的魔鬼数字,可以在代码周围增加说明注释,也可以定义局部 const 变量,变量命名自注释。
对于广泛使用的数字,必须定义 const 全局变量/宏;同样变量/宏命名应是自注释的。
0 作为一个特殊的数字,作为一般默认值使用没有歧义时,不用特别定义。
宏缺乏类型检查,不如函数调用检查严格。
宏展开可能会产生意想不到的副作用,如#define SQUARE(a) (a) (a)这样的定义,如果是 SQUARE(i++),就会导致 i 被加两次;如果是函数调用 double square(double a) {return a a;}则不会有此副作用。
以宏形式写的代码难以调试难以打断点,不利于定位问题。
宏如果调用的很多,会造成代码空间的浪费,不如函数空间效率高。
示例:下面的代码无法得到想要的结果:
#define MAX_MACRO(a, b) ((a) > (b) ? (a) : (b))
int MAX_FUNC(int a, int b) {
return ((a) > (b) ? (a) : (b));
}
int testFunc()
{
unsigned int a = 1;
int b = -1;
printf("MACRO: max of a and b is: %d\n", MAX_MACRO(++a, b));
printf("FUNC : max of a and b is: %d\n", MAX_FUNC(a, b));
return 0;
}
上面宏代码调用中,结果是(a < b),所以 a 只加了一次,所以最终的输出结果是:
MACRO: max of a and b is: -1
FUNC : max of a and b is: 2
“尽量用编译器而不用预处理”,因为#define 经常被认为好象不是语言本身的一部分。看下面的语句:
#define ASPECT_RATIO 1.653
编译器会永远也看不到 ASPECT_RATIO 这个符号名,因为在源码进入编译器之前,它会被预处理程序去掉,于是 ASPECT_RATIO 不会加入到符号列表中。如果涉及到这个常量的代码在编译时报错,就会很令人费解,因为报错信息指的是 1.653,而不是 ASPECT_RATIO。如果 ASPECT_RATIO 不是在你自己写的头文件中定义的,你就会奇怪 1.653 是从哪里来的,甚至会花时间跟踪下去。这个问题也会出现在符号调试器中,因为同样地,你所写的符号名不会出现在符号列表中。
解决这个问题的方案很简单:不用预处理宏,定义一个常量:
const double ASPECT_RATIO = 1.653;
这种方法很有效,但有两个特殊情况要注意。首先,定义指针常量时会有点不同。因为常量定义一般是放在头文件中(许多源文件会包含它),除了指针所指的类型要定义成 const 外,重要的是指针也经常要定义成 const。例如,要在头文件中定义一个基于 char*的字符串常量,你要写两次 const:
const char * const authorName = "Scott Meyers";
如果在宏定义中使用这些改变流程的语句,很容易引起资源泄漏问题,使用者很难自己察觉。
示例:在某头文件中定义宏 CHECK_AND_RETURN:
#define CHECK_AND_RETURN(cond, ret) {if (cond == NULL_PTR) {return ret;}}
然后在某函数中使用(只说明问题,代码并不完整):
pMem1 = VOS_MemAlloc(...);
CHECK_AND_RETURN(pMem1 , ERR_CODE_XXX)
pMem2 = VOS_MemAlloc(...);
CHECK_AND_RETURN(pMem2 , ERR_CODE_XXX) /*此时如果pMem2==NULL_PTR,则pMem1未释放函数就返
回了,造成内存泄漏。*/
所以说,类似于 CHECK_AND_RETURN 这些宏,虽然能使代码简洁,但是隐患很大,使用须谨慎。