阅读 18

避免宏定义产生副作用

在 native 编程中经常利用的宏,使用不当就会出现一些副作用

在编写 c/c++ 代码时,我们通常使用 #define 定义一些宏,然后编译器在 预处理 阶段会把用到宏的地方替换为宏的内容,由于宏的替换完全是一种文本替换,所以在一些情况下如果使用不当,容易产生副作用。

运算符优先级问题

考虑如下宏定义:

#define MAX(x,y) x > y ? x:y
复制代码

使用1:

int a  = MAX(1, 2);
复制代码

预处理后:

int a = 1 > 2 ? 1:2;
复制代码

预处理之后的代码,符合我们的预期。

使用2:

int b = 3 * MAX(1, 2);
复制代码

预处理后:

int a = 3 * 1 > 2 ? 1:2;
复制代码

这时预处理后的代码,就产生了副作用,由于 * 运算符的优先级高于 >,所以编译之后,优先执行 3 * 1,执行结果就成了 1,而不是期望的 6。

解决方法: 使用 () 包裹宏的内容,以避免优先级问题。

#define MAX(x,y) (x > y ? x:y)
复制代码

宏参数传递表达式问题

考虑如下宏定义:

#define MIN(x,y) (x < y ? x:y)
复制代码

这个宏的定义,就采用了 () 包裹,虽然避免了运算符优先级问题,但是还是存在副作用。考虑如下使用方式:

int a = MIN(i++, j++);
复制代码

预处理之后:

int a = (i++ < j++ ? i++ : j++);
复制代码

由于预处理是直接的文本替换,就会造成表达式会被执行两次,也产生了副作用。于是一般会在宏里额外定义局部变量来避免副作用:

#define max(type, x, y)({ \
    const type _x = (x); \
    const type _y = (y); \
    _x > _y ? _x : _y; })
复制代码
int a  = MIN(int, i++, j++);
复制代码

预处理之后:

int a = ({ const int _x = (i++); const int _y = (j++); _x > _y ? _x : _y; });
复制代码

保证了表达式只被执行一次。