【博客124】内核第一宏:container_of

内容: 记录一个特殊的宏,也是linux内核第一宏,就是container_of

定义式:

/**
 * 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 offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({          \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})

解释:

1.形参解释:member是type结构体中的成员,而指针ptr指向member成员,那么container_of宏的作用是
在type结构体类型已知和指向member成员的指针ptr已知的情况下得到type结构体的首地址。

2.typeof的作用是根据变量获取变量的类型。

3.container_of 宏的实现由一个语句表达式构成。语句表达式的值即为最后一个表达式的值:

(type *)( (char *)__mptr - offsetof(type,member) );

4.计算原理:结构体某个成员 member 的地址,减去这个成员在结构体 type 中的偏移,结果就是结构体 
type 的首地址。因为语句表达式的值等于最后一个表达式的值,所以这个结果也是整个语句表达式的值,
container_of 最后就会返回这个地址值给宏的调用者。

5.如何计算结构体某个成员在结构体内的偏移呢?
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

6.这个宏有两个参数,一个是结构体类型 TYPE,一个是结构体的成员 MEMBER,它使用的技巧跟我们上面计
算0地址常量指针的偏移是一样的:将0强制转换为一个指向 TYPE 的结构体常量指针,然后通过这个常量
指针访问成员,获取成员 MEMBER 的地址,其大小在数值上就等于 MEMBER 在结构体 TYPE 中的偏移。

因为结构体的成员数据类型可以是任意数据类型,所以为了让这个宏兼容各种数据类型。我们定义了一个临时
指针变量 __mptr,该变量用来存储结构体成员 MEMBER 的地址,即存储 ptr 的值。那如何获取 prt 指针
类型呢,通过下面的方式:
typeof( ((type *)0)->member ) *__mptr = (ptr);

这个宏的参数 ptr 代表的是一个结构体成员变量 MEMBER 的地址,所以 ptr 的类型是一个指向 MEMBER 
数据类型的指针,当我们使用临时指针变量 __mptr 来存储 ptr 的值时,必须确保 __mptr 的指针类型
是一个指向 MEMBER 类型的指针变量。typeof( ((type *)0)->member ) 表达式使用 typeof 关键字,
用来获取结构体成员 member 的数据类型,然后使用该类型,使用:
 typeof( ((type *)0)->member ) *__mptr 这行程序语句,就可以定义一个指向该类型的指针变量了。

注意的细节:
1.在语句表达式的最后,因为返回的是结构体的首地址,所以数据类型还必须强制转换一下,转换为 TYPE* ,
即返回一个指向 TYPE 结构体类型的指针,所以你会在最后一个表达之中看到一个强制类型转换(TYPE *)2.typeof( ((type *)0)->member ) *__mptr = (ptr);这个语句能够防止当传入的成员不是type
结构体成员的时候,那么不同类型的赋值会有警告,以此提醒你传入的形参不匹配

意义:

背景:
这个宏在内核中意义重大。Linux 内核中,对数据结构体进行了多次封装,往往一个结构体里面嵌套多层
结构体。也就是说,内核驱动中不同层次的子系统或模块,使用的是不同封装程度的结构体,这也是 C 语
言的面向对象思想。分层、抽象、封装,可以让我们的程序兼容性更好,适配更多的设备,但同时也增加了代码的复杂度。

作用:
我们在内核中,经常会遇到这种情况:我们传给某个函数的参数是某个结构体的成员变量,然后在这个函数中,
可能还会用到此结构体的其它成员变量,那这个时候怎么办呢?container_of 就是干这个的,通过它,我
们可以首先找到结构体的首地址,然后再通过结构体的成员访问就可以访问其它成员变量啦。

关于container_of就讲这么多啦,然后可能刚开始会觉得这个宏没有什么作用呀,我提个用到的地方
你去看看吧,linux内核的链表就用到了container_of宏,如果你一开始不知道这个宏,那么你对linux内核链表的结点定义里面没有数据域成员,一定是很懵的哦!快去看看吧,体会内核的精妙!

大四学生一枚,文章均非抄袭或者模仿,均为原创,仅代表个人观点,如果文章有错误的地方,欢迎在下方提出,每条评论我都会去认真看并回复,同时感谢指正的前辈。

发布了158 篇原创文章 · 获赞 34 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43684922/article/details/103334199