在linux 内核编程中,会常常见到一个宏函数container_of(ptr,type,member), 可是当你经过追踪源码时,像咱们这样的通常人就会绝望了(这一堆都是什么呀? 函数还能够这样定义??? 怎么还有0呢??? 哎,算了,仍是放弃吧。。。)。 这就是内核大佬们厉害的地方,随便两行代码就让咱们怀疑人生,凡是都须要一个过程,慢慢来吧。linux
其实,原理很简单: 已知结构体type的成员member的地址ptr,求解结构体type的起始地址。编程
type的起始地址 = ptr - size (这里须要都转换为char *,由于它为单位字节)。函数
到此,该函数已经讲完,是否是很简单??? 其实也不是,这里并无提到size如何计算,而令咱们头晕的正是这里。指针
好吧,先上container of函数原型:code
#define container_of(ptr, type, member) ({ \
blog
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
开发
(type *)( (char *)__mptr - offsetof(type,member) );})
原型
其次为 offserof 函数原型:源码
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
怎么样,是否是很炫? 好吧,下面开始揭开面纱:io
(一)0 指针的使用 (本身给的名字,不知有木问题)
让事实说话:
#include<stdio.h>
struct test
{
char i ;
int j;
char k;
};
int main()
{
struct test temp;
printf("&temp = %p\n",&temp);
printf("&temp.k = %p\n",&temp.k);
printf("&((struct test *)0)->k = %d\n",((int)&((struct test *)0)->k));
}
编译运行,能够获得以下结果:
&temp = 0xbf9815b4
&temp.k = 0xbf9815bc
&((struct test *)0)->k = 8
什么意思看到了吧,自定义的结构体有三个变量:i,j,k。 由于有字节对齐要求,因此该结构体大小为4bytes * 3 =12 bytes. 而&((struct test *)0)->k 的做用就是求 k到结构体temp起始地址的字节数大小(就是咱们的size)。在这里0被强制转化为struct test *型, 它的做用就是做为指向该结构体起始地址的指针,就是做为指向该结构体起始地址的指针,就是做为指向该结构体起始地址的指针, 而&((struct test *)0)->k 的做用即是求k到该起始指针的字节数。。。实际上是求相对地址,起始地址为0,则&k的值即是size大小(注:打印时由于须要整型,因此有个int强转)因此咱们即可以求咱们须要的 size 了 。 好吧,一不当心把 offsetof() 函数的功能给讲完了:::
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
此次再看就顺眼了吧(底层为何是这样我仍是不懂。。。只知道这样确实能够) , 因此offsetof()的做用就是求咱们求之不得的size, 并以size_t形式返回(size_t: 无符号整型)。
(二) 内核编程的严谨性
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
这里咱们只看第二行:
const typeof( ((type *)0)->member ) *__mptr = (ptr);
它的做用是什么呢? 其实没什么做用(勿喷勿喷,让我把话说完),但就形式而言 _mptr = ptr, 那为何要要定义一个同样的变量呢??? 其实这正是内核人员的牛逼之处:若是开发者使用时输入的参数有问题:ptr与member类型不匹配,编译时便会有warnning, 可是若是去掉改行,那个就没有了,而这个警告偏偏是必须的(防止出错有不知道错误在哪里)。。。这严谨性能够吧
typeof( ((type *)0)->member )
它的做用是获取member的类型仅此而已。至此基本结束
(三) 总结
container_of(ptr, type,member)函数的实现包括两部分:
1. 判断ptr 与 member 是否为赞成类型
2. 计算size大小,结构体的起始地址 = (type *)((char *)ptr - size) (注:强转为该结构体指针)
如今咱们知道container_of()的做用就是经过一个结构变量中一个成员的地址找到这个结构体变量的首地址。 container_of(ptr,type,member),这里面有ptr,type,member分别表明指针、类型、成员。