这个宏在驱动和内核代码中用的非常广泛,下面我们来具体分析下。
container_of作用是通过结构体某个成员地址从而拿到整个结构体地址。
原型:container_of(ptr, type, member)
示例:现有一个student结构体变量并初始化。
struct student stu;
stu.num = 1;
stu.score = 100;
strcpy(stu.name, "zhangsan");
现在我们假设有结构体某个成员的地址,比如score成员的地址也就是&stu.score,那么怎么拿到整个结构体的地址呢?
这就是上面宏的作用,看下每个成员分别代表什么:
ptr:代表结构体成员的真实地址
type:结构体的类型
member:结构体成员的名字
对于上面的例子,假如我们有个函数参数需要传递score成员的地址,而函数内部需要打印出原结构体变量的num的值,
只需要做如下操作:
//ptr代表score成员的地址
void func(float *ptr)
{
struct student *tmp; //定义一个结构体指针
tmp = container_of(ptr, struct student, score); //先获取到结构体变量的地址
printf("num = %d\n", tmp->num); //打印下
}
用起来还是非常简单的,下面看下内核具体的实现:
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
看起来比较复杂,我们以上面的例子对宏进行替换得到如下两句:
const typeof(((struct student *)0)->score) * __mptr = (ptr);
(struct student *)((char *)__mptr - offsetof(struct student, score));
第一句定义了一个__mptr指针指向了ptr,也就是指向了score成员的地址,
前面的typeof(((struct student *)0)->score)
作用是取得结构体中score成员的类型,属于gcc的一个关键字用法。
第二句话用__mptr(score成员的地址),减去score成员在原结构体中的偏移值,就得到了原结构体变量的地址。
第二句中又牵扯到一个新的宏:offsetof,它的作用就是求某个结构体成员在结构体的偏移值。看下原型:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
替换上面的
offsetof(struct student, score)
得到结果就是:
((size_t) &((struct student *)0)->score)
非常巧妙,先把0地址转换为一个指向student的结构体类型的指针,然后取出其score成员的地址,
因为这个地址是相对于0地址的,所以本身值就代表成员的偏移量,size_t是对地址进行的强转。
我们再回到container_of的原型,其实它的第一句话定义新的指针完全没有必要,那么做只是为了规范性,完全可以改成如下的定义,效果一样。
#define container_of(ptr, type, member) ({ \
(type *)( (char *)ptr - offsetof(type,member) );})
也就说我们直接用score成员的地址减去它的偏移量即可,是不是好理解多了。
最后,来个完整的测试用例:
#include <stdio.h>
#include <string.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
(type *)( (char *)ptr - offsetof(type,member) );})
struct student{
char name[10];
int num;
};
//num只是num成员的地址
void func(int *num_addr)
{
struct student *test;
test = container_of(num_addr, struct student, num);
printf("%s\n", test->name);
}
int main()
{
struct student stu;
strcpy(stu.name, "zhangsan");
stu.num = 3;
func(&stu.num);
}
内核的list核心链表最关键的实现就是container_of,理解上面的内容更有助于我们学习list链表。