最近看到一个宏定义如下:
/**
* rt_container_of - return the member address of ptr, if the type of ptr is the
* struct type.
*/
#define rt_container_of(ptr, type, member) \
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
因为程序中多次使用,所以对其进行了详细分析,过程如下:
这个宏的作用就是通过一个结构体的某个成员的指针,反推出这个结构体的首地址。这种操作非常有用,尤其在C语言中写面向对象的程序时,相当模拟了C++多态的效果。先写个小程序做个实验,验证一下(&((type *)0)->member)到底是什么结果:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
typedef struct {
char a;
char b;
char c;
char d;
}test_t;
void main(void)
{
printf("offsetof: %d\n", offsetof(test_t, a));
printf("offsetof: %d\n", offsetof(test_t, b));
printf("offsetof: %d\n", offsetof(test_t, c));
printf("offsetof: %d\n", offsetof(test_t, d));
}
当把结构体成员类型换一下,如下:
typedef struct {
int a;
int b;
int c;
int d;
}test_t;
现在我们可以知道(&((type *)0)->member) 这种操作,就是返回member在type这种类型结构体中的相对地址偏移,注意是相对偏移而不是成员在内存中的地址。理解了以上内容后再看offsetof(),它的作用是获取结构体中某个成员相对于该结构体首元素地址的偏移量。test_t 对象内存布局:
从图中可以看出,只要获得了某个成员的地址,再减去这个成员在结构体中的偏移就到到顶了,即反推出了结构体的地址。再理解rt_container_of这个宏定义就是(char *)(ptr)强转换以将移动步进控制为字节,ptr地址减掉这个偏移,就回到结构体首地址了。
linux中有同样的定义如下:#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })。这里面还出现了一个typeof关键字.那么,typeof(((type *)0)->member), (&((type *)0)->member) 到底是什么样的一种操作了?ANSI C标准允许值为0的常量被强制转换成任何一种类型的指针,并且转换的结果是个NULL,因此((type *)0)的结果就是一个类型为type *的NULL指针.如果利用这个NULL指针来访问type的成员当然是非法的,但typeof( ((type *)0)->member )是想取该成员的类型,所有编译器不会生成访问type成员的代码,类似的代码&( ((type *)0)->member )在最前面有个取地址符&,它的意图是想取member的相对于type类型首地址的偏移,所以编译器同样会优化为直接取地址。