内核经典的实现--container_of

这个宏在驱动和内核代码中用的非常广泛,下面我们来具体分析下。

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链表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浓咖啡jy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值