container_of()这个宏定义的功能是根据一个已知结构体成员的指针和变量名得出宿主结构体的地址
为方便理解和描述,本文中将已知的结构体成员叫做功能成员,它所在的结构体叫做宿主结构体。
先来分析一下参数:
ptr:指向功能成员的指针,存放功能成员的地址
type:宿主结构体的类型
member:功能成员在结构体中的表示(变量名)
container_of(ptr, type, member)的完整宏展开:
container_of(ptr, type, member) ({
const typeof( ((type*)0)->member ) *__mptr = (ptr) ;
(type*)( (char *)__mptr - offset(type, member) ) ; })
第2行,可能大伙看得有点蒙,先来分解一下这个语句的结构
我们定义一个变量的格式是:
修饰符+变量类型+变量名 = 右值;
修饰符 变量类型 变量名 右值
const typeof( ((type*)0)->member ) *__mptr = (ptr) ;
现在看明白了吗,抛开具体细节,“typeof( ((type*)0)->member )”代表的是一种数据类型,
那么它是什么样的数据类型呢?
((type*)0):它把0转换为一个type类型(也就是宿主结构体类型),为什么要这样做,且看后文
((type*)0)->member:这个0指针指向结构体中的member成员
typeof是gcc的c语言扩展保留字,用于获取变量的类型
typeof( ((type*)0)->member ) *:得出member的数据类型
所以,第2行的结果就是定义一个指向member的指针,并赋值为ptr
第3行,我们先看后半部分
offset(type, member);
offsetof定义在/include/linux/stddef.h中
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
根据前面的讲解可以知道:&((TYPE *)0)->MEMBER获得MEMBER的地址,此时&stu=0,因为结构体的基地址为0,所以MEMBER自然为其在结构体中的偏移地址。当然也可以把0换成1,这时&stu=1,要想得出功能成员相对于宿主结构体的偏移量还得在后面减去1,所以用0的好处就是使得功能成员的地址是相对于宿主结构体的偏移量。回到第2行,将0改为其他的任意正整数如“1”,并不影响最后的结果,因为在第二行中的目的仅仅是获得member的数据类型而已。
(char *)__mptr - offset(type, member):用第2行获得的结构体成员地址减去其在结构体中的偏移值便可得到宿主结构体的地址
可能有些同学觉得疑问,为什么要把__mptr转换为char类型的指针呢,C语言中,一个指向特定数据类型的指针减1,实际上减去的是它所指向数据类型的字节大小sizeof(data),所以这里要把它转换成char类型,不然得不出正确结果
(type*)( (char *)__mptr - offset(type, member) )
最后把地址转换成宿主结构体的类型type
由此,container_of()实现了根据一个已知结构体成员的指针和变量名得出宿主结构体的地址的功能
开山第一篇,如有错误,敬请指出!