副作用(Side Effect)
在计算机当中,副作用指当调用一个函数时,这个函数除了返回一个值之外,还对主调函数产生了影响,比如修改了全局变量,修改了参数等等。
宏的重复副作用
对于求两个数中的最小数,常常可以定义一个宏 MIN,定义如下:
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
上面的宏在写法上完全没有问题。但是假如有下面的代码:
当宏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 中也可以看到相关配置:
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。