文章目录
- 前言
- container_of函数介绍
- container_of函数实现
- container_of函数解析
- offsetof的使用
- container_of的使用
- 结语
前言
前两篇我们写到内核中两种C语言高级语法__attribute__, __read_mostly。本篇写内核中另外一种常用宏定义之container_of
container_of函数介绍
container_of是内核中使用最为常用的一个函数了,简单来说,它的主要作用是根据结构体中的已知的成员变量的地址,来寻求该结构体的首地址,直接看图,更容易理解。
container_of函数实现
5.10内核源码定义位置:include/linux/kernel.h(不同的内核函数实现会小有不同)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
¦!__same_type(*(ptr), void), \
¦"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
传入三个参数
- ptr, 表示结构体中的某一成员变量
- type, 表示结构体的类型
- member, 表示该变量在结构体中的成员具体命名
container_of函数解析
该宏定义下面有三个语句:
- void *__mptr = (void *)(ptr); \
-
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \ ¦!__same_type(*(ptr), void), \ ¦"pointer type mismatch in container_of()"); \
-
((type *)(__mptr - offsetof(type, member))); })
从语法角度,我们可以看到,container_of 宏的实现由一个语句表达式构成。说到container_of宏就必须说下offsetof宏语句,因为语句表达式的值即为最后一个表达式的值:
((type *)(__mptr - offsetof(type, member))
最后一句的意义是拿到结构体成员member的地址,减去这个成员在结构体type中的偏移,结果就是结构体type的首地址。那么如何获取结构体的偏移呢?内核用定义了offsetof宏来定义实现这个功能。
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
这个宏有两个参数,一个是结构体类型TYPE,一个是结构体成员MEMBER,就是获得0地址常量指针的偏移是一样的,如果是结构体的首地址为0的话,那么获取的变量地址既是绝对地址也是结构体内的偏移。
offsetof的使用
不用内核offsetof宏,我们求地址偏移像下面这样
struct student{
int num;
int age;
int math;
};
int main(void)
{
printf("%p\n",&((struct student *)0)->num);
printf("%p\n",&((struct student *)0)->age);
printf("%p\n",&((struct student *)0)->math);
return 0;
}
在上面的程序中,我们没有直接定义结构体变量,而是将数字0,通过强制类型转换,转换为一个指向结构体类型为 student 的常量指针,然后分别打印这个常量指针指向的结构体的各成员地址。运行结果如下:
因为常量指针为0,即可以看做结构体首地址为0,所以结构体中每个成员变量的地址即为该成员相对于结构体首地址的偏移
在尝试代入用offsetof的宏
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
((type *)(__mptr - offsetof(type, member))); })
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
struct ST
{
int i;
int j;
char c;
};
int main()
{
printf("offset i: %d\n",offsetof(struct ST,i));
printf("offset j: %d\n",offsetof(struct ST,j));
printf("offset c: %d\n",offsetof(struct ST,c));
return 0;
}
运行结果如下:
这样就可以理解结构体成员是以4字节对齐进行偏移的
container_of的使用
理解了offsetof的使用,我们就可以在写个demo来理解下container_of的使用
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
((type *)(__mptr - offsetof(type, member))); })
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
struct ST
{
int member1;
int member2;
int member3;
};
int main()
{
struct ST stest;
printf("&stest: 0x%x\n",&stest);
printf("offsetof(struct ST,member3) : %d\n",offsetof(struct ST,member3));
printf("&stest.member1: 0x%x\n",&stest.member1);
printf("&stest.member2: 0x%x\n",&stest.member2);
printf("&stest.member3: 0x%x\n",&stest.member3);
printf("container_of member3: 0x%x\n", container_of(&stest.member3, struct ST, member3));
return 0;
}
运行结果如下
可以看到stest和stest.member1的地址和container_of计算的首地址一样的
画个图可能大家更好理解
结语
相信大家对内核宏container_of的语法有所了解,如果我的文章对你有所收获的话,可以点赞关注转发给你小伙伴们
作者潘小帅, 是一名Linux底层爱好者,不定期写写技术原创文章和分享职场面试文章,徒步,旅游,看电影的爱好,喜欢我的文章可以点赞收藏+关注,感谢你的支持,微信公众号【Linux随笔录】
参考文章:
https://blog.csdn.net/xi_xix_i/article/details/134625972
https://zhuanlan.zhihu.com/p/672129384
本文由mdnice多平台发布