宏定义中的重复副作用

副作用(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 中也可以看到相关配置:

 

posted @ 2022-03-19 14:47  chaoguo1234  阅读(405)  评论(0编辑  收藏  举报