深入浅出实例解析linux内核container_of宏
做一件事情首先应该知道它的目的是什么。
container_of的目的:如何通过结构中的某个变量获取结构本身的指针。
总体思路:假想一下,你的结构体中有好几个成员,你如何通过里面的“任一成员”获取整个结构体的首地址呢。container_of的做法就是通过typeof定义一个与“任一成员”同类型的指针变量pvar_a(假设变量名就是pvar_a),并让指针变量pvar_a指向这个“任一成员”,然后用 “pvar_a的地址” 减去 “pvar_a相对于你的结构体的偏移量”,这个结果对应的地址其实就是你的结构体的首地址,最后将这个得到的结果强制转换为你的结构体对应的类型就OK了。
关于container_of见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) ({ /
const typeof( ((type *)0)->member ) *__mptr = (ptr); /
(type *)( (char *)__mptr - offsetof(type,member) );})
注:补充一点C语言关于宏定义的知识,在一个宏中,如果有多条语句,则最终宏的返回结果是最后一条语句的值,这有点像逗号表达式。
关于offsetof见stddef.h中:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
TYPE是某struct的类型,(TYPE *)0是将0地址处强制转换为TYPE类型struct,MEMBER是该struct中的一个成员(你完全可以理解成就是我上面说的“任一成员”). 由于该struct的基地址为0,所以MEMBER的地址本身毫无疑问之意就是代表它自身的地址,另外它还巧妙的有另外一层含义就是MEMBER的地址数值上等于MEMBER相对与struct头地址的偏移量,现在你该明白之前为什么要选(TYPE *)0来强制转换,而不选(TYPE *)100,或者其他。
关于typeof,这是gcc的C语言扩展保留字,用于声明变量类型.
“const typeof( ((type *)0->member ) *__mptr = (ptr);” 的意思是声明一个与member同一个类型的指针常量 *__mptr,并初始化为ptr.
“(type *)( (char *)__mptr - offsetof(type,member) );” 的意思是__mptr的地址减去member在该struct中的偏移量得到的地址, 再转换成type型指针. 其实该指针就是member的入口地址了.
下面是我结合以前在网上的一个例子修改后的一个新例子,希望对读者有帮助:
#include<stdio.h>
/*
*在应用层不能引用内核提供的container_of和offsetof宏,故需自己定义
*/
#define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) );})
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/*
*测试用的结构体
*/
struct student{
char name[20];
char sex;
}stu={"liuhb569620660",‘M‘};
int main(int argc, char *argv[])
{
struct student *stu_ptr; //存储container_of宏的返回值
int offset; //存储offsetof宏的返回值
/*
*下面三行代码等同于 container_of(&stu.sex, struct student, sex )
*/
/*
首先定义一个 _mptr指针, 类型为struct student结构体中sex成员的类型,typeof用于获取(((struct student*)0)->sex)的类型,此处该成员类型为char,((struct student*)0)在offsetof处讲解
*/
const typeof(((struct student*)0)->sex) *_mptr = &stu.sex;
/*
((struct student*)0)是把0地址强制转化为指向student结构体类型的指针,该指针从地址 0 开始的 21个字节 用来存放name与sex,(注:char name[20]与char sex共21字节),sex存放在第20个字节出(从0字节开始),&((struct student *)0)->sex 取出sex地址(此处即为20),并强制转化为整形,所以offset为20,后面的printf结果将证明这一点
*/
offset = (int)(&((struct student *)0)->sex);
/*
((char*)_mptr - offset)此处先把_mptr指针转化为字符形指针,(为什么这么做呢? 如果_mptr为整形指针 _mptr - offset 相当于减去 sizeof(int)*offset个字节),减去 offset值 相当于 得到_mptr所在结构体的首地址(即stu的地址),然后我们把 该地址 强制转化为 struct student 类型即可正常使用了
*/
stu_ptr = (struct student *)((char*)_mptr - offset);
printf("\n### offsetof stu.sex = %d\n",offset);
printf("### stu_ptr->name = %s \n" "### stu_ptr->sex = %c\n\n", stu_ptr->name, stu_ptr->sex);
/*
下面增加验证直接调用container_of这个宏效果
*/
/*通过sex成员找到结构体首地址*/
printf("\n### container_of(&stu.sex, struct student, sex)->name = %s\n", container_of(&stu.sex, struct student, sex)->name);
printf("### container_of(&stu.sex, struct student, sex)->sex = %c\n", container_of(&stu.sex, struct student, sex)->sex);
/*通过name成员找到结构体首地址*/
printf("\n### container_of(&stu.sex, struct student, sex)->name = %s\n", container_of(stu.name, struct student, name)->name);
printf("### container_of(&stu.sex, struct student, sex)->sex = %c\n", container_of(stu.name, struct student, name)->sex);
return 0;
}
例子的演示效果如下:
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。