linux操作系统,可以说它就是程序猿的代码天堂;这不仅仅因为它是开源的,更多的是因为它的诞生,是由世界上无数的代码天才共同缔造而来;跑在它上面的linux内核,经受了世界上各式各样的服务器压力测试,始终保持着高效、稳定、安全的特性,一如既往地服务全人类。甚至可以说linux操作系统造福了人类,很难想象,当linux操作系统消失了,这个世界会变得怎么样? 作为linux操作系统的忠实粉丝,笔者自大学时期就开始研究和使用linux操作系统,出来工作了好几年,几乎每天都要跟linux系统打交道,甚至毫不夸张的是,白天不在linux系统命令行下敲几行命令,晚上都会失眠。
学习和使用了linux系统这么些年,一直想找个机会,对linux的知识做一番梳理,无奈之前碍于各种时间因素和自我的惰性,迟迟未有实质性的进展。最近才开始狠狠地下定决心,必须迈出扎实的一步,争取做出更多的分享,充实自我的同时,也给同行带来更多的视野和思路,何乐而不为呢?
本文打算从一个很小的代码设计,试图从中窥探一下linux内核代码的精妙设计。它的名字就叫 max宏定义,请跟随笔者的思路一步步解开它神秘的面纱。
先来一个它的全貌:
#define max(a, b) ({\typeof(a) _max1 = (a);\typeof(b) _max2 = (b);\(void)(&_max1 == &_max2);\_max1 > _max2 ? _max1 : _max2; }) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aqsfb6ab-1661923667090)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
我们先不一下子就把这段代码剖析彻底,换个思维,假设我们是linux内核的设计者,要解决比较2个数的大小,代码应该怎么样入手。我想很多c语言工作者,甚至是初学c语言的码农也可以写出这样的如下代码:
#define max(a, b) a > b ? a : b [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jvjzddsr-1661923667093)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
初看这个宏定义,似乎没有问题;细细一看,用个测试案例一测试就发现端倪了:
/* 假设有如下的调用代码 */{ printf(result = %d\n, max(9!=9, 0==0)); /* 宏定义展开后是 9!=9 > 0==0 ? 9!=9 : 0==0*/ /* 输出结果是 0*/} [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-59bp6ggt-1661923667094)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
很明显正确答案应该是输出1,细心者就很快发现,给a和b加上括号试试看:
#define max(a, b) (a) > (b) ? (a) : (b) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k7e0tqdq-1661923667097)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
调用代码测试下:
/* 假设有如下的调用代码 */{ printf(result = %d\n, max(9!=9, 0==0)); /* 宏定义展开后是 (9!=9) > (0==0) ?(9!=9):(0==0)*/ /* 输出正确结果 1*/ printf(result = %d\n, 9 + max(9!=9, 0==0)); /* 宏定义展开后是 9 + (9!=9) > (0==0) ?(9!=9 :(0==0)*/ /* 输出结果是0, 正确的期望值输出,应该是10 (=9+1) */} [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m705rxon-1661923667114)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
于是又有了下面的改进:
#define max(a, b) ((a) > (b) ? (a) : (b)) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xrir0pv8-1661923667116)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
这个版本,也是我们日常写代码最经常看到的版本,我们使用测试代码测试下看看:
/* 假设有如下的调用代码 */{ printf(result = %d\n, 9 + max(9!=9, 0==0)); /* 宏定义展开后是 9 + ((9!=9) > (0==0) ?(9!=9):(0==0))*/ /* 输出正确的期望值10 (=9+1) */ int a = 8; int b = 9; printf(result = %d\n, max(a++, b++)); /* 宏定义展开后是 ((a++) > (b++) ?(a++):(b++))*/ /* 输出结果是10;而正确的期望值输出,应该是9 */} [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2jaec1eg-1661923667122)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
很遗憾,经测试,这个版本依然有问题,这是因为宏定义中的++操作干扰了比较结果的输出,我们需要再次改进这个宏定义。应该怎么样改进呢?既然是++操作干扰了输出,那么我们使用2个中间变量来中转下不就ok了吗?于是有了下面的版本:
#define max(a, b) ({\int _a = (a);\int _b = (b);\_a > _b ? _a : _b;\}) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t7vd8szr-1661923667123)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
这样的写法,已经有点接近linux内核定义的模样了。再次使用上面的测试代码执行测试:
/* 假设有如下的调用代码 */{ int a = 8; int b = 9; printf(result = %d\n, max(a++, b++)); /* 宏定义展开后是 ({int _a=a++; int _b=b++; _a > _b ? _a : _b;})*/ /* 输出正确的期望值9 */} [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dpinr0ot-1661923667125)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
虽然上面版本的定义解决了++操作符引起的输出结果错误的问题,但是由于宏定义内部使用了int型的_a和_b作为中间变量,这就是限制了max宏定义只能用于2个int型的数据做比较,这将大大限制了它的使用范围。于是,很容易想到一个解决办法,将int这个数据类型使用type变量传进去,于是有了下面的版本:
#define max(type, a, b) ({\type _a = (a);\type _b = (b);\_a > _b ? _a : _b;\}) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wvfbravf-1661923667126)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
这样的确是解决了如上数据类型问题的困惑,但是这样我们的宏定义是以多一个参数输入为牺牲代价的。那么,我们有没有什么办法,可以不将type输入,而直接从输入的a和b中获取它们的数据类型呢?答案是肯定有的!
gnu c作为c语言的扩展版本,增加了若干非常有用的扩展语法,其中typeof关键字就是其中的一个。比如定义一个变量int a; 则typeof(a)就可以取得a变量的类型,即int;比如直接使用typeof(unsigned char *),得到的输出就是数据类型unsigned char *,非常的实用。于是我们将typeof应用到max宏中,于是就有下面的优良版本:
#define max(a, b) ({\typeof(a) _a = (a);\typeof(a) _b = (b);\_a > _b ? _a : _b;\}) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ktnbdo0c-1661923667127)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
这样的写法,虽然避免了我们传递a和b变量的数据类型进去,但是,如下的测试代码,结果会怎么样呢?
/* 假设有如下的调用代码 */{ int a = 8; float b = 9.0; printf(result = %d\n, max(a, b)); /* 这样能比较吗?*/ int a = 8; float b = 9.0; float *p = &b; printf(result = %d\n, max(a, p)); /* 这样又能比较吗?*/} [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j0v9a1vg-1661923667128)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
很明显,当a是int型,而b是float型,内核执行比较是可以的;但是如果拿一个int型的变量跟一个float *变量做比较,或者两个奇奇怪怪的struct类型变量做计较,这样肯定是不行的。所以,我们在设计max宏定义的时候,需要将这种可能出现的问题尽可能地在编译阶段就暴露出来,于是有了linux内核max宏定义的最佳版本:
#define max(a, b) ({\typeof(a) _a = (a);\typeof(a) _b = (b);\(void) &_a == &_b;\_a > _b ? _a : _b;\}) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yvjm8aay-1661923667129)(data:image/gif;base64,r0lgodlhaqabapabap///waaach5baekaaaalaaaaaabaaeaaaicraeaow==)]
我们注意,宏定义的第4行,(void) &_a == &_b; 意思是对_a和_b的地址做比较,实际上从运行结果上,这个肯定是不等的,但是我们关心的并不是两者比较的结果,而是两者能不能用==比较的问题。当_a和_b的数据类型一致时,代码编译不会有任何警告;反之,当两者的数据类型不一致时,比如之前的a是int型,而b是float型,那么这条语句就会报出编译警告,如果在严格的编译选项下,这个警告还可以转换为错误,要求代码调用者去确认结果,是否对两个不同类型的数据执行max比较的动作,从而将隐患消除,提升代码质量。
通过跟随笔者的思路,我们可以细细地体会到,内核设计者在设计这个max宏时,相信也是走了不少的弯路,从一开始最简版本,接着遇到各式各样的问题,然后一步步解决,一步步完善设计,最终才有最优秀的max宏呈现在我们面前。如此之类的代码设计,在linux内核设计代码中比比皆是,今后笔者也会集中整理此类的优秀设计,致力于将更多的优秀内核代码分享给读者,敬请关注。文中提及的观点,均为笔者愚见,如有纰漏之处,还望诚心指正,谢谢。
网络安全形势依然严峻,各行业应做好迎接严峻的网络安全挑战
联想手机居然取得了增长 意味着这项业务还有发展的机会
元特:HITACHI环测代理商和售后服务商
深入探讨DDR5测试技术的最新技术
远程预付费电能管理系统概述、结构及功能
【Linux内核】从小小的宏定义窥探Linux内核的精妙设计
荣耀10青春版高清图赏
一种带背腔的陶瓷介质平面四臂槽螺旋天线设计浅析
微宏动力首次配套乘用车,打响了公司快充技术进军乘用车市场的第一枪
一文解析降压稳压器/转换器电路图设计
STC单片机控制DS1302程序 (C程序)
自动驾驶技术的“收官之战”:触发下一段产业赛程
华为p10价格破5000,值得入手吗?
骁龙865跑分越跑越高 那么性能真的在提升吗
表面贴片元件的手工焊接技巧
浅谈汽车电子电气域架构
AI监控人脸识别智能分析推动中控智慧发展
芯闻精选:高通确认收到华为18亿美元专利费,已申请出货许可
如何NAS上搭建web端vscode
小米正式发布小米智能眼镜