Clang 宏定义初探(一)
宏的定义方法是
#define
那么在什么场景下需要用到宏呢?遇到一些重复的东西,简单的有
for(i = 0; i < n; i ++) 之类的,为了减少繁琐的编码,可能使用
#define FO(i,N) for(i=0;i<N;i++)
为了增强可读性,比如说设置一个数组常亮大小,可以使用
#define N 1001
宏看起来感觉很好用,但是潜藏了很多问题,在实际使用中需要小心谨慎(当然带来大部分问题的,还是编码者自己或者合作方)。
例如,现在要求两个数的最小值,最初会写出如下宏:
#define MIN(A,B) A<B?A:B
正常情况下也是可以使用无误的:
1 #include <stdio.h>
2 #define MIN(A,B) A<B?A:B
3 int main()
4 {
5 int a =5, b =6;
6 printf("%d", MIN(a, b));
7 }
然而,当你发布你的代码或者很久以后自己再去调用时,可能写成:
MIN(a<4?a:5, b)
看起来也没啥问题,实际执行一下发现,结果是5,偏离预期!事实上,宏即使单纯的代码展开,当你展开上面的式子之后会发现,实际执行的代码是
a<4?a:5<b?a<4?a:5:b
对于一直用括号来解决优先级问题的我来说,这种展开完全无法理解,于是参看了内核代码,发现更安全的写法为:
#define MIN(A,B) (A)<(B)?(A):(B)
这种写法规避了令人厌恶的优先级问题!本以为这样就完成了一个安全的宏定义,但是事实上还有其他问题!
float a = 1.0f;
float b = MIN(a++, 1.5f);
printf("a=%f, b=%f",a,b);
神秘的a++,同样按照刚才的思路展开,因为宏里面会有两次A的展开,所以a++将被执行两次,再次偏离预期。对于这种情况,我们需要用到一个GNU C的赋值扩展,即使用({...})的形式。这种形式的语句可以类似shell,在顺次执行之后,会将最后一次的表达式的赋值作为返回。举个简单的例子,下面的代码执行完毕后a的值为3,而且b和c只存在于大括号限定的代码域中:
int a = ({
int b = 1;
int c = 2;
b + c;
});
结果是// => a is 3
基于这种特性,我们可以在宏里面为每个传入的参数进行一个拷贝,然后再对拷贝后的参数进行实际的比较运算,那么最终实现了一个比较安全的最小值比较的宏定义:
#define min(x,y) ({ \
typeof(x) __min1 = (x); \
typeof(y) __min2 = (y); \
(void) (& __min1 == & __min2); \
__min1 < __min2 ? __min1 :min2})
最后这一步跳跃有点大了,首先是 typeof 作用是得到参数的类型,其次是(void) (& __min1 == & __min2); 这个神秘的写法是为了验证二者的类型是否一致,当然外侧我们确实用了GNU的扩展({...})。
写到这里,涉及到了另外两个问题,GNU扩展是啥?另外我们用宏是为了节省代码,同时为了省去一些小函数的多次重复调用的参数入栈降低性能的问题,那么内联函数也有这样的效果,什么是内联函数呢?