宏定义中的重复副作用
副作用(Side Effect)
在计算机当中,副作用指当调用一个函数时,这个函数除了返回一个值之外,还对主调函数产生了影响,比如修改了全局变量,修改了参数等等。
宏的重复副作用
对于求两个数中的最小数,常常可以定义一个宏 MIN,定义如下:
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
上面的宏在写法上完全没有问题。但是假如有下面的代码:
int g = 0; // 全局变量 int foo(int i) { g++; // 函数foo有副作用,修改了全局变量 return i; } MIN(2 + 3, foo(4))
当宏MIN扩展之后,变成下面的样子:
((2 + 3) < foo(4) ? (2 + 3): foo(4))
看起来函数 foo 只调用了一次,但是当宏扩展后,foo 会被调用了2次。同时函数 foo 还有副作用,它会修改全局变量 g,当宏 MIN 调用完成之后,全局变量 g 的值成为了2,而不是1,这样可能会造成问题。
解决这个问题很简单,只需要让函数 foo 求值一次即可。标准 C 里面没有办法解决这个问题,而 GNU 对 C 的扩展提供了办法:
#define MIN(X, Y) \ ({ typeof (X) x_ = (X); \ typeof (Y) y_ = (Y); \ (x_ < y_) ? x_ : y_; })
上面的宏定义中,({...})定义了一个复合语句,它的值就是最后一个表达式的值。在这个复合语句里面,定义了两个局部变量 x_ 和 y_,后面比较大小都是使用 x_ 和 y_比较。typeof 是GNU C 里面的运算符,它与 sizeof 的使用十分类似,不同的是它返回的是参数的类型,如果参数是一个函数,那么返回的就是这个函数返回值的类型。
有了上面的定义,再次使用 MIN(2 + 3, foo(4)),经过宏扩展之后:
({ typeof (2 + 3) x_ = (2 + 3); typeof (foo(4)) y_ = (foo(4)); (x_ < y_) ? x_ : y_; })
可以看到函数 foo 只会被调用一次。注意 typeof (foo(4)) 并不会调用函数,只是返回函数返回值类型。
下面可以看下 iOS 中对宏 MIN 的定义:
上面 iOS 对于宏 MIN 的定义中,__NSX_PASTE__ 就是一个连接操作,__COUNTER__ 就是一个计数值。如果使用 iOS 中的 MIN 宏,那么 MIN(2 + 3, foo(4)) 就扩展为:
({ __typeof__(2 + 3) __a0 = (2 + 3); __typeof__(foo(4)) __b0 = (foo(4)); (__a0 < __b0) ? __a0 : __b0; })
可以看到在定义中并不是使用的 typeof,而是 __typeof__。typeof 是 GNU C (GNU 对标准 C 进行了扩展)支持的关健字,而 Clang 是兼容 GNU C的,所以也支持 typeof。同时标准 C 规定如果使用扩展,编译器的扩展需要使用双下划线,这也就是 __typeof__。从 Clang 的文档中可以看到这一点:
在 Xcode 中也可以看到相关配置: