Container_of

container_of 宏

Linux 内核第一宏。主要作用:

根据结构体某一成员的地址,获取这个结构体的首地址。

主要原理:

用结构体成员的地址,减去该成员在结构体内的偏移,即可得到该结构体的首地址。

1
2
3
4
5
6
7
8
9
10
11
12
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

/**
* 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) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })

container_of 宏三个参数:

  • type: 结构体类型
  • member: 结构体内的成员
  • ptr: 结构体内成员member的地址

container_of 宏实现分析

offsetof 宏

其功能是获得成员MEMBER在TYPE结构中的偏移量

结构体作为一个复合类型数据,里面可包含多个变量。当我们定义一个结构体时,编译器要为其在内
存中分配空间。根据每个成员的数据类型和字节对齐方式,编译器会按照结构体中各个成员的顺序,在
内存中分配一片连续的空间来存储他们。

一个结构体数据类型,在同一编译环境下,各个成员相对于结构体首地址的偏移是固定不变的。
当结构体的首地址为0时,结构体中各个成员的地址在数值上等同于结构体各成员相对于结构体首地址的偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct student {
int age;
int num;
int math;
};
int main(void)
{
printf("&age = %p\n", &((struct student*)0)->age);
printf("&age = %p\n", &((struct student*)0)->num);
printf("&age = %p\n", &((struct student*)0)->math);
return 0;
}
````
在上面程序中,将数字0通过强制类型转换,转换为一个指向结构体类型student的常量指针
然后分别打印这个变量指针指向的各个成员地址。其运行结果如下:
```c
&age = 00000000
&num = 00000004
&math = 00000008

因为常量指针的值为0,即可以看作结构体首地址为0,
所以结构体每个成员变量的地址即该成员相对于结构体首地址的偏移
这正是offsetof宏的功能。

1
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

根据运算符优先级分析:

  • (TYPE *)0: 将0地址强制转化为一个指向TYPE类型的结构体常量指针
  • (TYPE *)0)->MEMBER: 通过常量指针,访问成员MEMBER
  • &((TYPE *)0)->MEMBER: 获取MEMBER成员的地址,地址值即为MEMBER成员在TYPE结构中的偏移量
  • (size_t) &((TYPE *)0)->MEMBER: 将地址值强制转化为size_t类型的整形数

const typeof(((type *)0)->member) * __mptr = (ptr);

结构体中的成员数据可以是任意数据类型,为了让这个宏兼容各种数据类型,定义了一个临时指针变量__mptr,用来存储结构体成员MEMBER的地址,即存储宏中参数ptr的值。

必须保证__mptrptr的指针类型一样,因此使用typeof关键字,用来获取结构体成员MEMBER的数据类型。

typeof 是 GNU C 新增的一个关键字,用来获取数据类型。