其实,原理很简单: 已知结构体type的成员member的地址ptr,求解结构体type的起始地址。
type的起始地址 = ptr - size (这里需要都转换为char *,因为它为单位字节)。
到此,该函数已经讲完,是不是很简单??? 其实也不是,这里并没有提到size如何计算,而令我们头晕的正是这里。
好吧,先上container of函数原型:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
其次为 offserof 函数原型:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
怎么样,是不是很炫? 好吧,下面开始揭开面纱:
让事实说话:
#include
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
: 无符号整型unsigned int
)。
#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分别代表指针、类型、成员。
上述原理摘自:container of()函数简介
——————————————————————————————————————————————————————————————
下面是我自己的测试,以及拓展学习
先看代码:
#include
#include
/* 结构变量MEMBER到结构体首地址的偏移地址 */
#define OFFSET(TYPE,MEMBER) (char)&((TYPE*)0)->MEMBER
/* 通过结构体变量的首地址BODY找到它其中的某个成员BODY->MEMBER的地址
BODY,TYPE_BODY:分别是结构体变量的地址和类型
MEMBER,TYPE_MEMBER:分别是结构体成员的变量名和类型
*/
#define BODY_TO_MEMBER(BODY,TYPE_BODY,MEMBER,TYPE_MEMBER) (TYPE_MEMBER *)((char*)BODY + OFFSET(TYPE_BODY,MEMBER))
/*
通过一个结构变量中一个成员的地址找到这个结构体变量的首地址。
ptr,type,member分别代表指针、类型、成员
*/
#define container_of(ptr,type,member) (type *)((char*)ptr - OFFSET(type,member))
/* 举例测试 */
struct student{
char name;
int num;
};
int main()
{
struct student a;
a.name = '1';
a.num = 10;
printf("%d\n",OFFSET(struct student,num));
printf("%p\n",&a);
printf("%p\n",container_of(&a.num, struct student, num));
printf("%p\n",&a.num);
printf("%p\n",BODY_TO_MEMBER(&a, struct student, num, int));
return 0;
}
具体的代码以及注释已经写的很详细了,不再介绍了。