从数组的一个成员,反推出包含该数组的外围结构体
问题
驱动经常会注册各种回调,回调会对入参应用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
对象地址
注意事项
- 只要
回调函数入参
包含能标识自身数组索引
的字段,就可以使用本文所述的方法。 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;
}