指针的灵活应用--内核链表中的container_of
指针在C语言是一种很强大的武器,运用的好的话可以为我们很好的服务,这里我们以内核链表中的一个宏container_of,来分析编写内核代码的大佬们是如何巧妙运用指针的。
我们先直接给出container_of的定义
1 /** 2 * container_of - cast a member of a structure out to the containing structure 3 * @ptr: the pointer to the member. 4 * @type: the type of the container struct this is embedded in. 5 * @member: the name of the member within the struct. 6 * */ 7 8 #define container_of(ptr, type, member) ({ / 9 const typeof( ((type *)0)->member ) *__mptr = (ptr); -/ 10 (type *)( (char *)__mptr - offsetof(type,member) );})
在container_of中,我们又发现有一个typeof和offsetof,我们先来分别解释一下它们。
1.offsetof
offsetof的定义如下:
1 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
offsetof的意思是,在求结构体TYPE中,MEMBER相对于TYPE起始地址的偏移量。
offsetof是0指针的灵活运用。我们可以直接用代码测试。
1 /************************************************************************* 2 > File Name: mypointer.c 3 > Author: yudongqun 4 > Mail: qq2841015@163.com 5 > Created Time: Thu 05 Nov 2020 10:20:30 AM CST 6 ************************************************************************/ 7 #include <stdio.h> 8 #define NAME_LEN 20 9 #define offset(type, member) ((size_t)&((type *)(NULL))->member) 10 typedef struct { 11 char name[NAME_LEN]; 12 int school_number; 13 } Students; 14 15 int main(void) { 16 Students stu; 17 printf("%lu\n", offset(Students, school_number)); 18 printf("%lu\n", (size_t)((void *)&stu.school_number - (void *)&stu)); 19 return 0; 20 }
编译并运行:
ydqun@VM-0-9-ubuntu trash % ./a.out [0] 20 20
可以看到,我们通过offsetof求出了school_number成员在Students结构体中的位置,即school_number相对于Students结构体中的偏移量。
2.typeof
typeof关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛。仔细学习可以访问http://gcc.gnu.org/onlinedocs/gcc/Typeof.html#Typeof。我们这里只讲一下在contianer_of中typeof的用法。首先在#define container_of(ptr, type, member)中,type是结构体的名字,member指结构体type中的成员,ptr指某个type结构体变量中member的地址,我们单独把这语句代码const typeof( ((type *)0)->member ) *__mptr = (ptr),来写一个程序验证一下它的效果。
1 /************************************************************************* 2 > File Name: mypointer.c 3 > Author: yudongqun 4 > Mail: qq2841015@163.com 5 > Created Time: Thu 05 Nov 2020 10:20:30 AM CST 6 ************************************************************************/ 7 #include <stdio.h> 8 #define NAME_LEN 20 9 typedef struct { 10 char name[NAME_LEN]; 11 int school_number; 12 } Students; 13 14 int main(void) { 15 Students stu = { 16 .name = "Tom", 17 .school_number = 1234 18 }; 19 typeof(((Students *)0)->school_number)* p = &stu.school_number; 20 printf("*p = %d, stu.school_number = %d\n", *p, stu.school_number); 21 printf("p = %p, &stu.school_number = %p\n", p, &stu.school_number); 22 return 0; 23 }
编译并运行。
ydqun@VM-0-9-ubuntu 20201031 % gcc my_typeof.c [0] ydqun@VM-0-9-ubuntu 20201031 % ./a.out [0] *p = 1234, stu.school_number = 1234 p = 0x7ffe0a2e99f4, &stu.school_number = 0x7ffe0a2e99f4
由测试结果得知,typeof( ((type *)0)->member ) *__mptr = (ptr)的作用就是获取结构体type中member的类型去定义一个指向该member类型的指针mptr,并把指针ptr赋值给mptr,其中ptr是某个type结构体中的变量member的地址。
至此,我们已经分析完offsetof和typeof,现在可以直接分析container_of了。
3.container_of
其实container_of已经没啥好分析的了,const typeof( ((type *)0)->member ) *__mptr = (ptr),这一句代码是把指针ptr赋值给同类型的mptr。而宏定义#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)),是巧妙运用了0指针地址去求成员member在结构体type中的偏移量。那么我么知道成员member的地址ptr和偏移量,直接用member的地址去减去偏移量,就求出member成员所在的结构体变量的地址了,这就是container_of的作用。说到这里,也许你会觉得为啥要多执行const typeof( ((type *)0)->member ) *__mptr = (ptr);这一步操作,其实,这一步是防止开发者使用container_of时,输入参数有误,如果参数ptr输入错误,既ptr指向的类型和member的类型不匹配,编译器就会报错,所以container_of的严谨性真是让人叹服,不得不佩服内核代码编写者的代码功底。
最后,我们直接给出container_of的测试代码。
1 /************************************************************************* 2 > File Name: container_of.c 3 > Author: yudongqun 4 > Mail: qq2841015@163.com 5 > Created Time: Thu 05 Nov 2020 05:16:36 PM CST 6 ************************************************************************/ 7 8 #include <stdio.h> 9 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 10 #define container_of(ptr, type, member) ({\ 11 const typeof( ((type *)0)->member ) *__mptr = (ptr);\ 12 (type *)( (char *)__mptr - offsetof(type,member) );}) 13 14 #define NAME_LEN 20 15 typedef struct { 16 char name[NAME_LEN]; 17 int school_number; 18 } Students; 19 20 void func(int *p) { 21 Students *stu = container_of(p, Students, school_number); 22 printf("name: %s, school_number: %d\n", stu->name, stu->school_number); 23 } 24 25 int main(void) { 26 Students stu = { 27 .name = "Tom", 28 .school_number = 1234 29 }; 30 func(&stu.school_number); 31 return 0; 32 }
ydqun@VM-0-9-ubuntu 20201031 % gcc container_of.c [0] ydqun@VM-0-9-ubuntu 20201031 % ./a.out [0] name: Tom, school_number: 1234