本帖最后由 阿松松 于 2015-1-16 13:07 编辑
最近看的代码里面用到了container_of宏定义
- #define container_of(ptr, type, member) ({\
- const typeof( ((type *)0)->member ) *__mptr = (ptr);\
- (type *)( (char *)__mptr - offsetof(type,member) );})
复制代码
很是迷惑,百度之,竟然有原型分析,嗯,特来记下,与大家分享。
Enjoy it。
linux内核container_of宏定义分析
一、#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER )
1. ( (TYPE *)0 ) 将零转型为TYPE类型指针;
2. ((TYPE *)0)->MEMBER 访问结构中的数据成员;
3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址;
4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型;
巧妙之处在于将0转换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址。
举例说明:
#include<stdio.h>
typedef struct _test
{
char i;
int j;
char k;
}Test;
int main()
{
Test *p = 0;
printf("%p\n", &(p->k));
}
答案:00000008
自己分析:这里使用的是一个利用编译器技术的小技巧,即先求得结构成员变量在结构体中的相对于结构体的首地址的偏移地址,然后根据结构体的首地址为0,从而得出该偏移地址就是该结构体成员变量在该结构体中的偏移,即:该结构体成员变量距离结构体首的距离。在offsetof()中,这个member成员的地址实际上就是type数据结构中member成员相对于结构变量的偏移量。对于给定一个结构,offsetof(type,member)是一个常量,list_entry()正是利用这个不变的偏移量来求得链表数据项的变量地址。
二、container_of()
container_of() 来自\linux\kernel.h
内核中的注释:container_of - cast a member of a tructure 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 truct.
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
自己分析:
1.(type *)0->member为设计一个type类型的结构体,起始地址为0,编译器将结构体的起始的地址加上此结构体成员变量的偏移得到此结构体成员变量的偏移地址,由于结构体起始地址为0,所以此结构体成员变量的偏移地址就等于其成员变量在结构体内的距离结构体开始部分的偏移量。即:&(type *)0->member就是取出其成员变量的偏移地址。而其等于其在结构体内的偏移量:即为:(size_t)(& ((type *)0)->member)经过size_t的强制类型转换后,其数值为结构体内的偏移量。该偏移量这里由offsetof()求出。
2.typeof( ( (type *)0)->member )为取出member成员的变量类型。用其定义__mptr指针.ptr为指向该成员变量的指针。__mptr为member数据类型的常量指针,其指向ptr所指向的变量处。
3.(char *)__mptr转换为字节型指针。(char *)__mptr - offsetof(type,member))用来求出结构体起始地址(为char *型指针),然后(type *)( (char *)__mptr -offsetof(type,member) )在(type *)作用下进行将字节型的结构体起始指针转换为type *型的结构体起始指针。
4.({ })这个扩展返回程序块中最后一个表达式的值。
这就是从结构体某成员变量指针来求出该结构体的首指针。指针类型从结构体某成员变量类型转换为该结构体类型。
|
|
唉,遇到了才有机会了解到。还是不明白,为啥非要通过这么拐弯抹角的方式来求得结构体的首指针呢。。。
嗯,大概有些明白了,因为有时候向函数传递的变量仅仅是结构体内部的一个成员,这个成员属于某一个结构体。
在函数内部的时候,定义一个结构体指针,指针指向的地址没关系,因为我们要通过后续的成员变量来确定此结构体首指针。但是又需要用到整个结构体,所以需要把这个结构体成员映射到这个结构体中去。这样的话,我们就需要知道这个结构体的首指针了,从而实现对结构体中所有成员的调用。
如下:
嗯,描述的词语可能太不专业了,多包涵,多交流。
又有些想法,我觉得,为什么在传递参数的时候传递结构体的成员呢,如果直接传递结构体指针不是更好?
是不是又带来了其他的问题:
1、在调用函数中定义结构体指针,会导致无意义的大内存分配?
2、会导致主题函数的结构体初始化复杂?
请大神出来讲课
嗯,这个宏定义的关键是ptr要指向正在被使用的某类型变量,而这类型的变量又是type类型的一个成员,这样的话,member成员与ptr指向的类型就是一样的了。
另一篇文章里面的思路也不错,也摘抄过来。
container_of(ptr,type,member) 用于在已知结构体里面成员member和该成员指针ptr(就是地址)和结构体类型type, 返回该成员所在的结构体的指针(就是地址), 例如已知
实现该方法可以分三步:
0 算出a.age在a里面的偏移,可以通过将零地址强制转换成struct student格式, (struct student *)0, 那么 ((struct student *)0)->age 即是偏移大小
1 已知a.age地址和在a里面的偏移,即可通过a.age 地址减去偏移得到a的地址, (char *) page - ((struct student *)0)->age
2 最后将得到的地址强制 转换成struct student 格式, (struct student *)((char *)page - ((struct student *)0)->age) 即为所求的指针
根据我们的思路看内核里面的实现, 果然跟我们所想的一样
直接专递结构体的指针就行了。指针传递又不增加任何开销。
如果为了验证一个所谓的“高深”语句而去画蛇添足式的自找麻烦,岂不是得不偿失。
这是linux内部的一个应用,它是为了兼容其它接口而采取的一种折中方案,不是让大家模仿使用的。
前辈说的这些话,让我感觉很透彻,膜拜,求抱大腿。
这个代码是最近熟悉的一个SDK开发包中看到的代码结构,无奈看的云里雾里,只好百度之。。
不知道这个SDK中这样的写法合理性如何,话说回来,兼容其他接口而采取的一种折中方案,这句话怎么理解?
比如有一个接口在linux初期已经存在了,后来就被广泛使用到很多地方,现在如果要更新这个接口,其带来的麻烦可能是毁灭性的,但又要添加新功能该怎么办,又要兼容原来的,又要能加入新功能,只好“投机取巧”。
这就是思考的局限性,计算机刚开始大家觉得32位整数不会到头,现在64位出来了,IP地址也是类似。
这就是总得留点后路给自己,实际中发现,使用结构体其可扩展性还是非常好的。
嵌入式还是讲究简单、可靠。
我楼上才是真大神也,都来听课了。
@wangshu2013,膜拜ing
嗯,有一点儿感觉,自己还要慢慢体会。
谢谢前辈指点。