typeof关键字在内核中的运用

ANSI C定义了sizeof关键字,用来获取一个变量和数据类型在内存中所占的存储字节数。GNU 扩展了一个关键字,typeof用来获取一个变量或表达式的类型。

int i;
typeof(i) j = 20;
typeof(int *) a;
int f();
typeof(f()) k;

在上面的代码中,因为变量i的类型为int,所以typeof(i)就等于int,typeof(i) j = 20就相当于int j = 20;typeof(int *) a;就相当于int * a;函数也是有类型的,函数的类型就是其返回值类型,所以typeof(f()) k;就相当于int k;。

typeof( typeof(int *)[5] ) a; //相当于int * a[5];
typeof( int x[5]) y; //相当于int y[5];

完美MAX(a,b)宏的诞生

在之前黑鸟的文章中,有提到过如何定义一个适用不同类型且无副作用的MAX宏。今天就带大家复习一下:

青铜级别:

#define MAX(x,y) ( (x) > (y)?(x):(y) )

一般我们在教科书上看到的简单的MAX宏定义如上。但他有两个致命缺陷,一是两个参数必须类型一致,二是参数不能带副作用,即自增或自减。原因大家自己思考,当然也可以查看我之前的文章。

白银级别:

#defineMAX(type,x,y)({ \
    type _x = x; \
    type _y = y; \
    _x > _y ? _x : _y; \
})

该方法通过添加一个type参数解决了不同类型变量比较的问题;通过定义临时变量_x和_y成功解决了参数的副作用问题。但他还不是最好的。

黄金级别:

#define MAX(x,y) ({\
typeof(x)_x=x;\
typeof(y)_y=y;\
_x>_y ? _x : _y;\
})

通过typeof直接获取宏的参数类型,这样我们就不必再单独将参数的类型传给宏了。到这里他已经基本可以在江湖中有自己一席之地了,但离号令天下还有一步之遥。

砖石级别:

#defineMAX(x,y)({\
    typeof(x) _x = x; \
    typeof(y) _y = y; \
    (void) ( &_x == &_y ); \
    _x>_y?_x:_y;\
})

该宏定义之所以称之为钻石级别,是因为他多了一句(void)(&_x==&_y); 该语句看起来貌似一句废话,但其实用的很巧妙。它主要是用来检测宏的两个参数的数据类型是否相同。如果不相同,编译器会给一个警告信息提醒开发人员。

waring:comparison of distinct pointer types lacks a cast
  • 让我们分析一下他是如何实现判断他的两个参数的数据类型是否一致。

从字面意思来看,他用来判断两个变量的地址是否相等。可能有人会说,两个变量的地址怎么可能相等呢?但妙就妙在这个地方!当该语句还未执行到判断两个变量的地址是否相等的时候,编译器首先要检查两个变量的数据类型是否相同。如果两个变量的数据类型不相同的话,那么编译器会有警告信息。当然我们也就可以从中获益。而如果两个变量的数据类型相等的话,那么该语句在整个宏中也不起任何作用,同样我们也没任何损失,笑问这种无本生意为何不做呢?

所谓是驴子是马拉出来溜溜!上面我们讲了这么多,但sizeof关键字到底有什么作用呢?下面通过解剖内核宏container_of来为大家展示sizeof关键字的强大作用和内核设计者的巨大脑洞。

解剖内核第一宏container_of

首先让我们来膜拜一下天下第一宏的风采:

#define offsetof( TYPE, MEMBER ) ( (size_t)&((TYPE *)0)->MEMBER )
#define container_of(ptr, type,member) ({ \
    const typeof( ((type *)0)->member ) _mptr = (ptr); \
    (type  *)( (char *)_mptr - offsetof(type, member) );
})

它的主要作用就是:根据结构体某一成员的地址,获取这个结构体的首地址。根据宏定义我们可以看到,这个宏有三个参数,他们分别是:

  • type:结构体类型
  • member :结构体内的成员
  • ptr: 结构体内成员member的地址

也就是说,只要我们知道了一个结构体的类型,结构体内某一成员的地址,就可以直接获得这个结构体的首地址。这个宏返回的就是这个结构体的首地址。

container_of宏实现分析

作为一名Linux内核驱动开发者,除了要面对各种手册、底层寄存器,有时候还要应付底层造轮子的事情。为了系统的稳定和性能,有时候我们不得不深入底层,死磕某个模块进行分析和优化。底层的工作虽然很有挑战性,但有时候也是很枯燥的。不像应用开发那样有意思,所以为了提高对工作的兴趣,大家表面上虽然不说自己牛叉,但内心深处一定要建立起自己的职位优越感。人不可有傲气,但不能无傲骨。我们可不像开发,知道api接口、读读文档、完成功能就ok了。作为一名底层开发者要时刻记住:要和寄存器、内存、硬件电路等各种底层群众打成一片。从群众中来,到群众中去,急群众之所急,想群众之所想。这样才能构建一个稳定和谐的嵌入式社会。

  • 首先来看offsetof宏
#defineoffsetof(TYPE,MEMBER)((size_t)&((TYPE*)0)->MEMBER)

这个宏有两个参数,一个是结构体类型TYPE,一个是结构体的成员MEMBER。它使用的技巧就是:将0强制转换为一个指向TYPE类型的结构体常量指针。因为常量指针为0,即可以看做结构体首地址为0,所以结构体每个成员变量的地址即为该成员相对于结构体首地址的偏移。最后通过强制类型转换size_t,将成员地址值转换为整数,取得其在整个结构体中的偏移。

  • 再来看container_of宏
consttypeof(((type*)0)->member)_mptr=(ptr);

因为结构体的成员数据类型可以是任意的数据类型,所以为了让这个宏兼容各种数据类型,我们定义了一个临时指针变量_mptr,该变量用来存储结构体成员member的地址ptr。那如何获取ptr指针类型的呢?就是通过上面的宏语句。

(type*)((char*)_mptr-offsetof(type,member));

最后通过该成员变量地址_mptr减去该成员在结构体中的偏移,就得到了结构体的首地址。当然因为返回的是结构体的首地址所以数据类型还必须强制转换一下。

小思

好了到这里我们对天下第一宏的分析也就接近尾声了。那么可以看出任何一个复杂的东西,我们都可以把它分解。运用所学的基础知识一层一层,一点以一点去剖析,先去降维分析,然后再进行综合。那么这种能力就是你的核心竞争力,也是你超越其他工程师脱颖而出的机会!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容