近日在看红黑树的相关知识,在Linux的红黑树源代码中看到了container_of,甚是不解,于是仔细看了一下,总结一下。
首先在include\linux\kernel.h中找到定义如下:
#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
(1)typeof是什么?未在c语言中(c++)见过;
(2)((type*)0)的用法;
(3)offsetof又是什么意思?
(4)这个宏定义的用法,以及实现的功能。
下面一个个来看:
(1)typeof
搜索一下发现是c语言新加的关键字,在linux系统中广泛使用。
使用方法:typeof(类型) 、 typeof(表达式)
测试一下:
int a = 10; typeof(&a) pa = &a; printf( "%d\n", *pa );或者
int a = 100; typeof(int *) pa = &a; printf( "%d\n", *pa );
因此typeof的作用是取出括号中的类型,并且可以用来定义新的变量,于是可以将第一句简化为
const typeof( 表达式) * mptr=(ptr); //就是定义了一个新的变量,变量的类型是表达式中的指针类型。
(2)((type*)0)->member
根据字面意思type应该是一种类型,又其包含数据成员member,所以是一个struct(在C语言中),
仔细想一下这个0的用法,是将0强制转换为一个指针,然后访问其member成员,由于现在的水平有限,
可以先这样理解:存在地址addr,假设其指向对象obj(类型为struct exper),将其转化为struct exper*,
并取出其member成员。细想一下若是发生在运行期间会出现访问0地址内存的错误,结合上述typeof
可以得出typeof( ((type*)0)->member )应该是一种编译期间动作,得出type结构中member 成员
的类型。
(3)offsetof
在include\linux\stddef.h中找到定义如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)同样((TYPE*)0)得到一个TYPE类型的指针,取出其MEMBER成员,细想一下可以这么理解,
0可以认为是这个结构体对象的基地址,从这个基地址开始取出某个成员MEMBER,再取出这个MEMBER
成员的地址:
成员MEMBER的地址 = 基地址(此处为0)+ 成员MEMBER在结构体中的偏移量
此处&(((TYPE*)0)->MEMBER)求的就是TYPE结构体重成员MEMBER的地址,因此得出的值就是
MEMBER在TYPE结构中的偏移量。
(4)宏定义的功能和用法
const typeof( ((type *)0)->member ) *__mptr = (ptr); //定义一个type中成员变量member类型的指针
同时这个指针用宏参数ptr初始化,可见ptr本身就是一个 type成员变量member类型的指针;
(type *)( (char *)__mptr - offsetof(type,member) ); //经过偏移计算,得出的是一个(type*)指针
根据上面的偏移公式,这里计算的是: 成员member的地址 - 成员member在结构体中的偏移量
因此得出的就是该结构体的基地址,也就是起始地址。
从而,可以将该宏的功能总结一下:根据结构体中某个成员的地址,得出这个结构体本身的地址。
下面举个例子说明一下这个宏的用法:
假设有结构体如下:
struct dma_chan_ref { struct dma_chan *chan; struct list_head node; struct rcu_head rcu; atomic_t count; };函数如下:
static void free_dma_chan_ref(struct rcu_head *rcu) { struct dma_chan_ref *ref; ref = container_of(rcu, struct dma_chan_ref, rcu); kfree(ref); }以上代码摘抄自linux源代码,但是还不知道是什么意思,此处只是引用一下,说明一下本文的问题:
函数需要根据结构体(struct dma_chan_ref)中成员rcu(struct rcu_head)的地址来获得结构体
struct dma_chan_ref的起始地址,然后进行kfree,调用参数如下:
1)第一个参数rcu为函数中的形参,代表的是结构体中rcu成员的地址;
2)第二个参数是基础结构体的名字struct dma_chan_ref;
3)第三个参数rcu代表的是在基础结构体struct dma_chan_ref中第一个指针参数rcu实体在结构体中
的名字。
也就是第一个参数是个结构体成员的指针,第二三两个参数分别是结构体名字和成员名字。