container_of简介

container_of是很重要的一个功能宏,它可以基于一个成员来取得成员所在的结构体。

container_of定义和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 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))); })
  • 参数1:成员的指针
  • 参数2:成员所在的结构体名字
  • 参数3:该成员在结构体里面的名字
  • 返回值:返回所在结构体的指针,类型是参数2指定的

使用示例:

1
2
3
4
5
6
7
8
struct a_dev {
struct device* dev;
int flag;
struct cdev c_dev;
}

struct cdev *my_cdev = ...;
struct a_dev *res = container_of(my_cdev, a_dev, c_dev);

container_of详解

container_of分为三个主要的组成部分,分别是小括号+花括号组成的赋值语句、offsetof语句、指针运算语句。

整体赋值

container_of本身是通过一对小括号和一对花括号组成的复合赋值语句。可以看到宏定义里面有三个语句(分号分割),然后用小括号({})包起来返回给调用者。伪代码表示的逻辑如下。

1
2
3
4
5
6
7
8
auto result = container_of(成员指针,结构体名称,成员在结构体中的名称);

#define container_of(成员指针,结构体名称,成员名称)
({
语句1-成员指针类型转为万能指针;
语句2-合法性判断;
语句3-offset计算和指针地址运算;
})

从C语言的({})赋值方法知道,({})的返回值就是最后一个语句的值,因此container_of宏的整体意义就是把运算后的指针返回给调用者。

offsetof计算成员到结构体头的偏移

offsetof的定义如下。

1
2
3
4
5
6
#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE, MEMBER) __compiler_offsetof(TYPE, MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#endif

其核心定义是((size_t)&((TYPE *)0)->MEMBER)。该语句分解如下。

  1. ((TYPE *)0):把地址0当作存储了一个TYPE,取为指针
  2. ((TYPE *)0)->MEMBER:struct结构体访问成员的编译原理实际上是指针偏移,这里利用->来“访问”MEMBER
  3. 加一个&:前面已经访问到了MEMBER,对它加上&,实际上等同于计算了地址0按struct TYPE头地址相对MEMBER的偏移,相当于假设地址0有一个TYPE数据,这一步就取到了MEMBER成员的地址,即MEMBER相对于TYPE头的偏移
  4. (size_t):进行强制类型转换,把偏移变成“大小”

选用0地址的原因是如果结构体头地址是0,那么其成员的地址就是偏移的大小

对0地址做了”->调用”,这里利用了编译器进行的地址加减偏移,实际上编译器编译后就是数值0的加减,并未发生调用,不会产生空指针问题。

指针运算

第二步我们已经获取到了MEMBER相对于TYPE的偏移大小,而我们本身就知道了MEMBER的地址,那么把MEMBER抵消掉这段偏移,得到的就是TYPE的地址。再做一个强制类型转换,就取得了MEMBER所在的TYPE了。

1
((type *)(__mptr - offsetof(type, member)));