container_of宏的一种高级用法

从数组的一个成员,反推出包含该数组的外围结构体

问题

驱动经常会注册各种回调,回调会对入参应用container_of宏,来得到外围对象的指针,从而访问外围对象的其他字段。
有的时候,回调函数的入参,并不是想要访问的结构体的儿子字段,而是孙子字段,甚至是重孙子字段,并且这种嵌套关系可能并不是简单的结构体嵌套,而是数组嵌套,例如这样的:

struct board{
    int slot;
    int ip;
};

struct chassis{
    int id;
    struct board boards[3];  // 数组嵌套
    int weight;
};

现在假设,我有第二个board对象的指针

struct chassis c = {
    .id = 10,
    .boards = {
        {1, 0xcafebabe},
        {2, 0xdeadbeef},
        {3, 0xaa55aa55}
    },
    .weight = 20
};

struct board *b = c.boards[1]; // 第二个board对象的指针!

但没法通过一阶container_of宏函数调用来获得chassis的指针,因为我不能这样

struct chassis *c2 = container_of(b, struct chassis, boards);
printf("%d, %d\n", c2->id, c2->weight);

或这样

struct chassis *c2 = container_of(b, struct chassis, board);
printf("%d, %d\n", c2->id, c2->weight);

来得到chassis对象的引用,因为前者指针值不匹配,后者字段名不匹配。

解决办法

笨办法

可以先得到boards数组的基地址,然后再得到chassis的地址:

// 先将结构体指针转型成char*指针,这样才能进行字节粒度的指针运算
// slot减1是因为槽位号从1开始,而数组下标从0开始
struct board *b0 = (struct board *)((char *)b - sizeof(struct board) * (b->slot -1));
struct chassis *c3 = container_of(b0, struct chassis, boards);

但这样进行2次类型转换显得很笨拙

更好的办法

想到将上面2步结合起来,并给container_of的参数1用上指针偏移运算根据类型自动调整字节数的机制:

struct chassis *c3 = container_of(b - (b->slot -1), struct chassis, boards);

更更好的办法

其实container_of宏的第三个参数同是可以为表达式的,t同样能引入指针偏移运算

struct chassis *c4 = container_of(b, struct chassis, boards[b->slot -1]);

根据board自带的slot槽位信息,算出b在boards结构体数组中的索引,然后就可以既满足指针值匹配,又满足字段名匹配,从而让container_of宏算出真正的chassis对象地址

注意事项

  1. 只要回调函数入参包含能标识自身数组索引的字段,就可以使用本文所述的方法。
  2. container_of宏的参数3必须是参数1指向的对象,而不是参数1指向的对象的指针,否则编译时container_of调用的类型检查宏BUILD_BUG_ON_MSG会报错:参数1参数3类型不匹配

总结

在技术上,人应该贪心点,时常想想:有没有更好的解决办法?

附录:完整实例代码

#include<stdio.h>

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

#define container_of(ptr, type, member) ({              \
    void *__mptr = (void *)(ptr);                   \
    ((type *)(__mptr - offsetof(type, member))); })

struct board{
    int slot;
    int ip;
};

struct chassis{
    int id;
    struct board boards[3];
    int weight;
};

int main()
{
    struct chassis c = {
        .id = 10,
        .boards = {
            {1, 0xcafebabe},
            {2, 0xdeadbeef},
            {3, 0xaa55aa55}
        },
        .weight = 20
    };
    // 根据board1的IP获取board1的地址
    struct board *b = container_of(&c.boards[1].ip, struct board, ip);
    printf("%d\n", b->slot);
    // 根据board1的地址获取chassis的地址
    struct chassis *c2 = container_of(b, struct chassis, boards[b->slot -1]);
    printf("%d, %d\n", c2->id, c2->weight);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值