最近闲来无事,分析下linux kernel里面一些函数都是怎样定义使用的,它们都是怎样避免风险的,从include/linux/kernel.h中挑出一个经典的min宏进行分析一下
从我们最先认识的min函数开始看看,这个函数的作用就是求两个数中小一点的那个
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
1)首先我们可以看到这个宏的定义外面由一个({}),为什么要这样写呢?其实{}很好理解,因为我们的宏是多个语句,假如没有了这个{},在一些语句中就会出现歧义,比如if(a)min(x,y),这样就会出问题,后面的语句它就不会执行了。那么这里为什么在外面还要加一个()呢,我们来看这个函数的作用,它是用来返回一个较小的数,对,是返回,一般来说对于返回值,我们怎么用?最简单的当然是return了,所以,一般来讲,我们会写return min(x,y);(当然还有很多常用的方法,比如说赋值等等,我这边的目的只是解释()呵呵~~)。这个时候你就不难理解我们为什么要加个()了吧,不过正规的来讲,这是GCC扩展中的一个用法,在GCC扩展中允许吧一个()内的复合语句看成是一个表达式,称为语句表达式,它可以出现在任何语句表达式可以出现的地方。
2)继续看这个宏,我们知道一般我们使用这个函数的时候,x,y是什么类型的并不清楚,我们是否需要所有的类型都写一个min函数呢?其实不需要,在GCC的扩展中,我们提供了一个宏,称为typeof,它可以获得相应参数的类型,这样就可以不需要因为类型而重写宏了,小方便啊。
再看这两句话:
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
为什么要把x,y赋值给_min1和_min2然后返回_min1和_min2,而不直接写成(x) < (y)? (x) :(y)呢?这个就是最经典的C语言课本中所说的那个所谓的++的问题了,还需要多说吗?好吧,我说一下吧,因为我也是菜鸟,也记不清当时是在什么地方提到这个东西了,呵呵,不过印象中记得好像是这样说的:假如我写成min(a++,b++),展开成宏就变成了(a++)< (b++)? (a++) :(b++)了,究竟a和b各自++了多少次,大概可以算出来吧,所以我们一定要先把它们赋值给两个局部变量,这样就没有这个问题了。
3)下面再来看一下那个(void) (&_min1 == &_min2);这句话好纠结啊,奶奶的,反正我是没看懂是什么作用,不过可以肯定的是肯定有它存在的意义,于是只好做试验了,好吧,我写写写,写了好几个代码发现两者都是一样的,怎么办呢?呵呵,google一下,终于发现有人讲这个了,说是为了在比较不同类型的时候能够打印一个警告信息。好吧,我试试看,写一个简单测试程序
#include
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
main(){
unsigned long a = 4;
in b = 5;
printf("min is %d\n", min(a, b));
}
用gcc编译了一下,果然报下面警告了:
min.c: In function ‘main’:
min.c:14: 警告:比较不相关的指针时缺少类型转换
没有这句话,就不会报这个警告,为什么会这样呢?跟阿虚讨论了下,原来,在比较地址的时候,会发现地址所存值的类型,假如不同的话就会报警告,而在直接比较值的时候,比如上面的_min1 < _min2 ?的时候,会做强制类型转换,而且不会报警告,好吧,我真的很佩服写这段代码的人,太牛了!简单的4,5行代码,竟然有这么多的东西在里面,这叫老夫如何是好啊,继续努力。