上回,我们在《Linux内核中max()宏的奥妙何在?(一)》一文中说到,在3.18.34版Linux内核源码中的max()宏,采用了GCC的扩展特性,可以避免一些错误。但是在Linux内核开源世界里,没有最好,只有更好,内核中的max()宏又被打补丁啦。让我们先来看看,Linus大神对于新补丁的看法:
是的,以前求最大值和最小值的宏还要定义各自的局部变量,而且变量名似乎并不是很贴切,后来又使用GCC的扩展__UNIQUE_ID()函数来进行唯一命名,这似乎还是有所欠缺。
拥抱变化,才会适应变化,那么最新的max()宏又变成什么样了呢?下面让我们来一探究竟。
在5.1.16版内核源码中,常见的比较大小的宏长这样:
/**
* min - return minimum of two values of the same or compatible types
* @x: first value
* @y: second value
*/
#define min(x, y) __careful_cmp(x, y, <)
/**
* max - return maximum of two values of the same or compatible types
* @x: first value
* @y: second value
*/
#define max(x, y) __careful_cmp(x, y, >)
/**
* min3 - return minimum of three values
* @x: first value
* @y: second value
* @z: third value
*/
#define min3(x, y, z) min((typeof(x))min(x, y), z)
/**
* max3 - return maximum of three values
* @x: first value
* @y: second value
* @z: third value
*/
#define max3(x, y, z) max((typeof(x))max(x, y), z)
我们拿#define max(x, y) __careful_cmp(x, y, >)
宏定义来分析,那么__careful_cmp(x, y, >)
又是什么呢?首先__careful_cmp(x, y, op)
这个函数前面加了 “__”表示他是一个Linux的内部函数,他有三个参数,分别是x,y和op。到此我们就可以看出当op为>时,函数功能为x和y中求最大数,当op为<时,函数功能为x和y求最小数。我们再来看看他的定义:
#define __careful_cmp(x, y, op) \
__builtin_choose_expr(__safe_cmp(x, y), \
__cmp(x, y, op), \
__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op))
那么__careful_cmp(x, y, op)
宏替换的函数
__builtin_choose_expr(__safe_cmp(x, y), \
__cmp(x, y, op), \
__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op))
又是何方神圣呢?
__builtin_choose_expr(__safe_cmp(x, y), \
__cmp(x, y, op), \
__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op))
这个函数是一个谓词函数,它属于编译时行为,而非运行时行为,跟sizeof和typeof一样,他的第一个参数必须是常量。
__safe_cmp(x, y)
的结果非0,则返回__cmp(x, y, op)
,且返回类型与__cmp(x, y, op)
的类型一致。__safe_cmp(x, y)
的结果为0,则返回__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op)
,且返回类型与__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op)
类型一致。__builtin_choose_expr()
宏第二个和第三个参数所产生的目标代码是互斥的,生成了__cmp(x, y, op)
就不会存在__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op)
。现在我们现在已经知道了__builtin_choose_expr()
函数的作用,其实就是就是根据第1个参数,选择返回第2个参数还是第3个参数。接下来,我们一起看看它的参数吧!
__safe_cmp(x, y)
它在内核中是这样定义的:
#define __safe_cmp(x, y) \
(__typecheck(x, y) && __no_side_effects(x, y))
嗯?又出现了两个陌生的函数!问题不大,我们继续看他们的定义:
#define __typecheck(x, y) \
(!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))
嗯?看不懂,但,问题依旧不大。首先,宏观上,他是(a==b)
型的,他不是一个函数,而是一个表达式。微观上,看内部,我先把他的括号一层一层扒开:
(typeof(x) *)
(typeof(y) *)
((typeof(x) *)1 == (typeof(y) *)1)
(sizeof((typeof(x) *)1 == (typeof(y) *)1))
(!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))
- typeof()是GCC提供的,是在预编译时处理的,作用是获取变量的数据类型。(typeof(x) *)是获取x的数据类型,如果x是int 型,(typeof(x) *)的返回值就是int *,如果x是double型,(typeof(x) *)的返回值就是double *。
((typeof(x) *)1 == (typeof(y) *)1)
就等价于(int * 1 == double * 1)
(sizeof((typeof(x) *)1 == (typeof(y) *)1))
实现了严格的类型检查(!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))
把表达式的结果置为1
到此,我们就知道了__typecheck(x, y)
宏其实是用来做一个严格的类型检查,无论怎么样,表达式的结果都是1。具体功能就是判断y的类型是否与x相同,如果类型不同,编译器会跑出一个警告,有图有真相。
从上面两个例子中可以看出__typecheck(x, y)表达式是做类型检测,类型不同则给出警告,表达式的值永远是1,不说了,看图好理解:
明白了表达式(__typecheck(x, y) && __no_side_effects(x, y))
中的第1个函数__typecheck(x, y)
,我们再看第2个函数__is_constexpr(y)
,看完源码再说:
#define __no_side_effects(x, y) \
(__is_constexpr(x) && __is_constexpr(y))
这又是啥啊,问题依旧不大,看了再说,先看看__is_constexpr()的定义:
#define __is_constexpr(x) \
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
嗯,很复杂,别怕,我们先来看这个部分:
((void *)((long)(x) * 0l))
((long)(x) * 0l)
是一个值为0的整数常量表达式,((void *)((long)(x) * 0l))
是一个空指针常量。((void *)((long)(x) * 0l))
不是一个空指针常量。
- (long)(x)强制转换旨在允许x具有指针类型并避免u64在32位平台上对类型发出警告。
- 值为0的整型常量表达式或类型为void *的表达式称为空指针常量。
我们接着看:
(8 ? ((void *)((long)(x) * 0l)) : (int *)8)
这个条件运算符会根据条件返回不同的类型:
最终的结果是这样的,不管其值,管其类型,我觉得大家都不想看了,我还是以图明意吧:
__cmp(x, y, op)
在下面这个函数中:
__builtin_choose_expr(__safe_cmp(x, y), \
__cmp(x, y, op), \
__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op))
当第1个参数__safe_cmp(x, y)
返回值为1的时候,__builtin_choose_expr()函数的返回值为__cmp(x, y, op)
函数的返回值,直接看__cmp(x, y, op)
函数源码:
#define __cmp(x, y, op) ((x) op (y) ? (x) : (y))
这个函数相对简单点,也是实现比较大小求最值的核心代码,需要注意的是这个函数第三个参数op,求最大值会被替换为>,求最小值会被替换为<。
__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op)
看起来貌似又很复杂,但问题依旧不大,先看源码:
#define __cmp_once(x, y, unique_x, unique_y, op) ({ \
typeof(x) unique_x = (x); \
typeof(y) unique_y = (y); \
__cmp(unique_x, unique_y, op); })
首先,__cmp_once()
这个函数有5个参数,分别是x、y、 unique_x、unique_y和op,x和y是要比较大小的两个数, unique_x和unique_y是唯一命名后的两个局部变量,op会被替换成>或小于,如果是求最大值,则op就是>,如果是求最小值,op就是<。
在这个函数要替换的表达式中,我们又看到GCC的扩展({statement list})和typeof()了,先回顾一下其功能:
- ({statement list})是一个表达式,类似于逗号表达式,但是功能更强,({语句1;语句2;语句3;})中可以包含有多条语句(可以是变量定义、复杂的控制语句),该表达式的值为statement list中最后一条语句的值。
- typeof()的功能是取变量类型。typeof(x)是获取x的类型,typeof(x) unique_x = (x);就是定义一个x的类型的变量 unique_x,并把x的数值赋值给它。如x是int型的5,那么typeof(x) unique_x = (x),就相当于int unique_x =5。与之前的命名_max1,_max2相比,这样的命名更加贴切。
首先,表达式的结构是这样({语句1;语句2;语句3;}),根据GCC的扩展特性,这个表达式最终的值应该是语句3的值。
语句1typeof(x) unique_x = (x);
是定义了一个同x的类型的唯一的局部变量unique_x,并把x的数值赋值给了unique_x。
语句2typeof(y) unique_y = (y);
;是定义了一个同y的类型的唯一的局部变量unique_y,并把y的数值赋值给了unique_y。
与之前的max()宏源码的不同之处在于,这里的unique_x和unique_y是经过函数__UNIQUE_ID(__x)和 __UNIQUE_ID(__y)唯一命名来传入的。__UNIQUE_ID()可以生成一个唯一的名字,唯一性是由编译器提供的__COUNTER__宏保证的,这也是GCC的一个扩展,在GCC文档中对其的说明如下:
This macro expands to sequential integral values starting from 0. In conjunction with the ## operator, this provides a convenient means to generate unique identifiers.
简而言之,__COUNTER__会被展开为一个从0开始的整数,且每次调用后其值都会加一,这也就保证了其命名的唯一性。
语句3__cmp(unique_x, unique_y, op);
又是如下宏的替换:
__cmp(x, y, op) ((x) op (y) ? (x) : (y))
用局部变量唯一命名的方式来进行比较大小,就会避免输入参数和宏定义内部使用的局部变量重名,而导致在宏定义的语句块外层同名变量被内层变量作用而出现错误,这也就避免了类似于x++和y++进行比较而出现错误了。
到此,我们就把5.1.16版max()宏的参数和作用都分析完了,好像有点复杂,但问题依旧不大,让我们宏观上来总结一下,且看5.1.16版内核max()宏的结构图:
当我们调用max(x,y)
时,他被替换成__careful_cmp(x, y, >)
,进而执行函数的第1个参数__safe_cmp(x, y)
。
__builtin_choose_expr(__safe_cmp(x, y), \
__cmp(x, y, op), \
__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op))
__typecheck(x, y)
返回1,无警告;若x和y是不同类型,则__typecheck(x, y)
返回1,有编译警告。__no_side_effects(x, y)
返回1;若x和y其中有一个不是常量表达式,则__no_side_effects(x, y)
返回0。__no_side_effects(x, y)
返回1,则__safe_cmp(x, y)
返回1;若__no_side_effects(x, y)
返回0,则__safe_cmp(x, y)
返回0。__safe_cmp(x, y)
返回1,则__builtin_choose_expr
函数返回__cmp(x, y, op)
的值,若__safe_cmp(x, y)
返回0,则__builtin_choose_expr
函数返回__cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op)
的值。max(x,y)
的返回值就是__careful_cmp(x, y, op)
的返回值,也就是__builtin_choose_expr
的返回值。到此,我们已经把5.1.16版内核max()宏的参数和作用都分析完了,那么max()宏是如何从诞生成长到现在的呢?它到底经历过什么,下篇文章让我们继续探索!