linux内核中存在container_of宏,其定义如下:
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: 指向结构体中member这个成员的指针.
* @type: 表示此结构体的类型.
* @member: 所求结构体中的某个成员,ptr参数为其地址.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
通过此宏可以通过某个结构体中某个已知成员(member)的首地址获得整个结构体的首地址。宏定义中巧妙的使用了0地址。
一、 const typeof( ((type *)0)->member ) *__mptr = (ptr);
1、typeof( ((type *)0)->member )中typeof是GNU C对标准C的扩展,在此处的作用是获取结构体中member 成员的类型。
2、再定义一个member参数类型相同的__mptr参数:const typeof( ((type *)0)->member ) *__mptr。
3、最后再将ptr的地址赋值给__mptr。至此使用了临时变量保存了ptr的地址。
二、 (type )( (char )__mptr - offsetof(type,member) );
此结构中有一个offsetof宏,定义如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
offsetof宏定义中的&((TYPE )0)->MEMBER也使用了0地址,将0地址强制转换为TYPE 类型,再求得MEMBER成员相对于0地址的偏移,最后将此偏移地址强制转化为整形:((size_t) &((TYPE *)0)->MEMBER)。
所以第二步利用offsetof(type,member) )求得member成员相对type首地址偏移后,再使用ptr地址减去偏移就是结构体类型的首地址了。
综上所述,在container_of和offsetof都使用了0地址才实现了目的。其实不使用0地址也可以达到同样的目的,只不过container_of和offsetofde这个地址要保持一致。例如下面代码对这两个宏进行了略微的更改,也能达到一样的效果。
#include<stdio.h>
/*略微的改动,增加了base参数,可以用来改变相对地址,当base=0时,和内核宏一样。*/
#define offsetof(BASE,TYPE, MEMBER) ((size_t) &((TYPE *)BASE)->MEMBER-BASE)
#define container_of(base,ptr, type, member) ({ \
const typeof( ((type *)base)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(base,type,member) );})
struct data_info
{
int i;
char ch;
float f;
};
int main(void)
{
struct data_info temp_st={3,'a',1.5};
int base = 7;
printf("&temp_st=0x%02x\n",&temp_st);
printf("container_of=0x%02x\n",container_of(base,&temp_st.ch,struct data_info,ch));
return 0;
}
上述代码执行结果如下截图,可以看到求出的值和结构体的起始值一致: