在Linux 内核中,container_of 函数使用非常广,例如 Linux内核链表 list_head、工作队列work_struct中。
在Linux 内核中有一个大名鼎鼎的宏container_of(),这个宏是用来干嘛的呢?其作用就是获取包含某个成员的结构体变量地址。我们先来看看它在内核中是怎样定义的。
我们先来分析一下 container_of(ptr, type, member); 这里面有 ptr, type, member 分别代表指针、类型、成员。看一个例子:
1 Struct test {
2 int i;
3 int j;
4 char k;
5 };
6 Struct test temp;
现在呢如果我想通过 temp.j 的地址找到 temp 的首地址就可以使用 container_of(&temp.j, struct test, j);
现在我们知道 container_of() 的作用就是通过一个结构变量中一个成员的地址找到这个结构体变量的首地址。
下面来看看比较复杂的内容:
我们用上面的 struct test 扩展一下
我们先看第一句话 const typeof( ((type* )0)->member )* __mptr = (ptr); // (sturct test* )0 表示数据段基址
const typeof( ((struct test* )0)->j )* __mptr = (&temp.j);
其中,typeof 是 GNU C 对标准 C 的扩展,它的作用是根据变量获取变量的类型。因此,上述代码的作用是首先使用 typeof 获取结构体成员 j 的类型为 int ,然后顶一个 int 指针类型的临时变量 __mptr ,并将结构体变量中的成员的地址赋给临时变量 __mptr。
看看第二句话,我们直接进行扩展:
(struct test* )( (char* )__mptr - offsetof(struct test, j) )
接着我们来看一下offsetof(struct test,j),他在内核中如下定义
对 offsetof 展开
(size_t) &( (struct test* )0 )->j; // (sturct test* )0 表示数据段基址, ((struct test* )0)->j,不就是 j 位于结构体中的位置吗!!!,(size_t) & 取它的地址转换成 size_t (int类型)的值。即得到偏移量!!!!
最终通过 (char* )__mptr (将 __mptr地址类型强转成char* 类型) - offsetof(type, member) (偏移量)不就得到了结构体的首地址
写个用例:
1 #include <stdio.h>
2
3 #undef offsetof
4 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)
5
6 #ifndef container_of
7 #define container_of(ptr, type, member) ({
8 const typeof( ((type*)0)->member ) *__mptr = (ptr);
9 (type* )( (char* )__mptr - offsetof(type, member) );
10 })
11 #endif
12
13 struct student {
14 int count;
15 char sex;
16 short age;
17 float score;
18 };
19
20 int main(int argc, char* argv[]) {
21 struct student xiaoming = {
22 .count = 50,
23 .sex = 1,
24 .age = 18,
25 .score = 98.5,
26 };
27
28 struct student* pXiaoming = NULL;
29
30 //此处的 container_of 得到的结果不需要进行类型强转,在宏定义的第二句话中已经对该地址做了强转
31 pXiaoming = container_of(&xiaoming.age, struct student, age);
32
33 printf("count : %d
", pXiaoming->count);
34 printf("sex : %d
", pXiaoming->sex);
35 printf("age : %d
", pXiaoming->age);
36 printf("score : %f
", pXiaoming->score);
37
38 return 0;
39 }
测试结果如下:
写在最后
- 第一行对于宏的结果并不是本质上重要的,但是它用于类型检查。
- 第二行真正的作用是什么?它从成员地址减去结构体成员的偏移量,从而得出结构体变量的实际地址。如此而已;