转自:http://blog.chinaunix.net/uid-30254565-id-5637597.html
- 内核中container_of宏的详细分析
-
16年2月28日09:00:37
-
内核中有一个大名鼎鼎的宏-----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 container_of(ptr, type, member) ({
-
const typeof( ((type *)0)->member ) *__mptr = (ptr);
-
(type *)( (char *)__mptr - offsetof(type,member) );})
-
先来说这个宏的意义:它根据结构体中某成员变量的指针来求出指向整个结构体的指针。指针类型从结构体某成员变量类型转换为 该结构体类型。
-
-
比如先定义一个结构体:
-
struct test {
-
char name[20] ;
-
char i;
-
int j;
-
};
-
假如,我们不小心知道了变量j的地址,那么我们想要通过j的地址来找到整个结构体test的地址,怎么来找呢???
-
-
(一) offsetof宏:
-
结构体是一个线性存储的结构,无论在哪存放,j相对于整个结构体的地址的偏移值是不变的,于是,如果我们能够求出来这个偏移值的话,那么用j的地址减去这个偏移值不就是整个结构体的地址么~这是一个朴素的想法,内核中也确实这么做的~关键是怎么求出这个偏移值?内核的非常聪明的采取了下面的方法:
-
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
-
(1)首先通过(TYPE *)0将0转换为TYPE类型的指针;
-
(2)((TYPE *)0)->MEMBER 访问结构中的数据成员;
-
(3)&(((TYPE *)0)->MEMBER)取出数据成员的地址;
-
(4)(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型; 注意这里:这个&是取地址符号,不是按位与,注意运算符号的优先级。
-
-
巧妙之处在于将地址0强制类型转换成(TYPE*),结构体以内存空间首地址0作为起始地址,则各个结构体成员变量的偏移地址就等于其成员变量相对于整个结构体首地址的偏移量 。即:&(((TYPE *)0)->MEMBER)就是取出其成员变量的偏移地址,(size_t)(&(((TYPE*)0)->MEMBER))经过size_t的强制类型转换以后,其数值为结构体内的偏移量。
-
需要明确的一点是,地址就是地址,它没有类型之分,你把它强制转换成什么类型它就是什么类型,所以在c语言中有各种强制类型转换。
-
用下面的例子来说明:
-
#include <stdio.h>
-
-
struct test {
-
char i;
-
int j;
-
int k;
-
};
-
-
int main(int argc, char const *argv[])
-
{
-
struct test *temp = 0;
-
-
printf("%p
", &(temp->j));
-
printf("%d
", (size_t) &(temp->j));
-
printf("%p
", &(temp->k));
-
printf("%d
", (size_t) &(temp->k));
-
-
return 0;
-
}
-
运行结果是:
-
0x4
-
4
-
0x8
-
8
-
可以看出来,通过采用这种方式,就可以求出来结构体中成员变量相对与整个结构体首地址的偏移量。
-
-
(二) container_of宏
-
如果理解了上面的部分,再看这个container_of宏就不是那么难了,我们先想想它怎么实现:
-
假设我们知道一个test类型的结构体里面的一个成员变量j的地址,那么需要先求出这个j变量相对于整个结构体地址的偏移值,然后用这个j的地址减去这个偏移值就行了。但是还有一点,这个j变量的数据类型是什么样的?这个虽然我们知道test数据类型,但是我们怎么取出来j对应的数据类型呢?这时候就用到typeof关键字了,typeof是GNU
C对标准C的扩展,它的作用是根据变量获取变量的数据类型。
-
下面来看这些代码:
-
#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,然后将它指向我们知道的member变量,然后通过typeof( ((type *)0)->member )来获得member对应的数据类型。
-
(2)const typeof( ((type *)0)->member ) *__mptr = (ptr);意思是声明一个与member同一个类型的指针常量 *__mptr,并初 始化为ptr.
-
(3)(char *)__mptr - offsetof(type,member)意思是__mptr的地址减去member在该struct中的偏移量得到的地 址, 这样得到的就是整个结构体的首地址。
-
(4)得到首地址后还没有完,上面说了,地址只是一个地址,它没有数据类型,所以最后再进行一一次强制类型转换,转换成我们需要的type类型的,即:
-
(type *)( (char *)__mptr - offsetof(type,member) );
-
(5)({ })这个扩展返回程序块中最后一个表达式的值。注意这个 container_of宏是两个表达式语句的综合。 相当与顺序执行了两个语句,这时候得到的地址就是member成员所在结构体的首地址。
-
要注意的是代码高亮处 ,(char *)__mptr 的作用是将__mptr 强制转换为字符指针类型,必须的!!!如果__mptr为整形指针 __mptr - offset 相当于减去sizeof(int)*offset个字节!!!
-
-
下面再看一个程序来温习一下:
-
-
#include <stdio.h>
-
-
#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) );})
-
-
struct test {
-
char name[20] ;
-
char i;
-
int j;
-
};
-
-
int main(int argc, char const *argv[])
-
{
-
struct test temp = {"zer0", 'a', 26};
-
-
printf("&temp = %p.
", &temp);
-
printf("&temp.i = %p.
", &temp.i);
-
printf("&temp.j = %p.
", &temp.j);
-
printf("offset of i = %d.
", offsetof(struct test, i));
-
printf("offset of j = %d.
", offsetof(struct test, j));
-
printf("&temp = %p.
", container_of(&temp.i, struct test, i));
-
printf("&temp = %p.
", container_of(&temp.j, struct test, j));
-
-
struct test *tmp = container_of(&temp.i, struct test, i);
-
printf("tmp->name : %s, tmp->i : %c, tmp->j : %d.
", tmp->name, tmp->i, tmp->j);
-
return 0;
-
}
-
-
运行结果如下所示:
-
&temp = 0xbf8b41b0.
-
&temp.i = 0xbf8b41c4.
-
&temp.j = 0xbf8b41c8.
-
offset of i = 20.
-
offset of j = 24.
-
&temp = 0xbf8b41b0.
-
&temp = 0xbf8b41b0.
- tmp->name : zer0, tmp->i : a, tmp->j : 26.