Linux version: 4.14
Code link: Linux source code (v4.14) - Bootlin
1 函数原型
对于 container_of [ include/linux/kernel.h: 927 ]
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
其中 offsetof [ include/linux/stddef.h: 19 ]
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
2 函数使用
container_of(ptr, type, member) 用于从包含在某个结构体中的指针获得结构体本身的指针,通俗地讲就是 通过结构体变量中某个成员的首地址进而获得整个结构体变量的首地址。
① ptr 参数指结构体某成员变量的首地址(指针)
② type 参数表示结构体类型
③ member 参数表示结构体中的成员变量名
④ 函数返回指是结构体的首地址(指针)
说明:在 container_of 宏定义中,下面这行代码的结果就是该宏的返回值
((type *)(__mptr - offsetof(type, member)));
下面给出一个示例演示:
struct mystruct {
int a;
int b;
int c;
};
struct mystruct temp;
int *p = &temp.a;
container_of(p, struct mystruct, a);
container_of(p, struct mystruct, a) 得到的就是 temp 变量的地址。
3 原理说明
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
(TYPE *)0 是将 0 强制转换成 TYPE 型指针,则该指针一定指向 0 地址(数据段基址)。
&((TYPE *)0)->MEMBER 这句话其实是 &(((TYPE *)0)->MEMBER) ,通过该指针访问 TYPE 的 MEMBER 成员并得到其地址。相对于结构体的起始地址 0 ,那么 &((TYPE *)0)->MEMBER 就是相对于起始地址之间的偏移量,这个偏移量对于所有的 TYPE 型变量都是成立的。offsetof(TYPE, MEMBER) 就表示这个偏移量。
void *__mptr = (void *)(ptr);
由于 offsetof() 函数求得的是偏移字节数(size_t 类型),而 __mptr 是 void* 类型,当指针类型为 void* 时,其加减运算与普通整数没有区别。所以二者相减便可以得到TYPE变量的起始地址,最后通过 (type *) 类型转换,将该地址转换为 TYPE 类型的指针。
补充:size_t 是一种无符号整数数据类型,如果编译器是 32 位,那么它只是 unsigned int
4 参考
一文解析linux内核之链表操作 - 知乎 (zhihu.com)