container_of宏在linux中的应用非常广泛,几乎随处可见,它的作用是通过一个结构体成员的地址来获得这个结构体的首地址。先来看一下定义:
#undef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
* 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) );})
三个成员的含义:
ptr:结构体成员的地址
type:结构体类型
member:结构体成员的名字
举例说明用法,比如有如下结构体:
struct data{
int i;
int j;
char c;
};
struct data value;
现在已知value.j的首地址,想获得value的首地址,可以使用container_of(&value.j, struct data, j);
下面来分析一下实现过程:
1、typeof是GNU C的扩展,作用是根据变量来获取变量的类型,对本例来说,就是获取了j的类型int,然后定义一个int型指针__mptr,并把j的地址赋给__mptr。
2、第二句中offsetof用来求某个结构体成员在结构体中的偏移值,在本例中,即j相对与结构体value的偏移,先把0地址转换为一个指向value的结构体类型的指针,然后取出其j成员的地址,因为这个地址是相对于0地址的,所以本身值就代表成员的偏移量,size_t是对地址进行的强转。
3、用j的地址减去j在结构体中的偏移,得到的即为结构体的首地址。
关于0指针
0指针在这里是不会崩溃的,因为这个表达式本身并不会被求值。编译器在乎的指示它的类型。编译器并不关心0指针的值,编译器会在分配给结构体的地址加上该成员的偏移,并且返回加上偏移之后的新地址。