linux内核中max宏奥妙何在,Linux内核中max()宏的奥妙何在?(二)——大神Linus对这个宏怎么看?...

最新max()宏

上回,我们在《Linux内核中max()宏的奥妙何在?(一)》一文中说到,在3.18.34版Linux内核源码中的max()宏,采用了GCC的扩展特性,可以避免一些错误。但是在Linux内核开源世界里,没有最好,只有更好,内核中的max()宏又被打补丁啦。让我们先来看看,Linus大神对于新补丁的看法:

c44c04acf5ef92617917d5173263cf4c.png

是的,以前求最大值和最小值的宏还要定义各自的局部变量,而且变量名似乎并不是很贴切,后来又使用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为#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相同,如果类型不同,编译器会跑出一个警告,有图有真相。

如果他们是不同类型:有警告,表达式结果是1

3335e8eb4454cc020453b3969a3ba6bd.png

如果他们是相同类型:一切风平浪静 ,表达式结果是1

38d4146ff3396dbe6ba3578f594b8ce7.png

从上面两个例子中可以看出__typecheck(x, y)表达式是做类型检测,类型不同则给出警告,表达式的值永远是1,不说了,看图好理解:

61c0cc17e9cb7b9888a6b5630c17f658.png

明白了表达式(__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))

如果x是一个整数常量表达式,那么((long)(x) * 0l)是一个值为0的整数常量表达式,((void *)((long)(x) * 0l))是一个空指针常量。

如果x不是一个整数常量表达式,那么((void *)((long)(x) * 0l))不是一个空指针常量。

(long)(x)强制转换旨在允许x具有指针类型并避免u64在32位平台上对类型发出警告。

值为0的整型常量表达式或类型为void *的表达式称为空指针常量。

我们接着看:

(8 ? ((void *)((long)(x) * 0l)) : (int *)8)

这个条件运算符会根据条件返回不同的类型:

如果x不是一个整数常量表达式,那上面的表达式会返回int *

如果x不是一个常量表达式,上面表达式会返回void *

最终的结果是这样的,不管其值,管其类型,我觉得大家都不想看了,我还是以图明意吧:

ef98478914fc7fab6ba272e84b8d729a.png

第二个参数__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()宏的结构图:

69b22295597942c553777f5a5add2e85.png

当我们调用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))

若x和y是同种类型,则__typecheck(x, y)返回1,无警告;若x和y是不同类型,则__typecheck(x, y)返回1,有编译警告。

若x和y都是常量表达式,则__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()宏是如何从诞生成长到现在的呢?它到底经历过什么,下篇文章让我们继续探索!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值