前言
在查看linux内核中,经常会看到container_of这个宏定义,第一眼看到这个宏定义中括号一层又一层,晕了。下面本文就来好好分析这个宏的具体实现。
内核版本:linux-2.6.34
container_of宏详解
container_of宏的源码定义在kernel.h文件中,具体如下:
/**
* 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) );})
下面先来看宏定义中的三个参数,如下图:
member是type结构体中的成员,而指针ptr指向member成员,那么container_of宏的作用是在type结构体类型已知和指向member成员的指针ptr已知的情况下得到type结构体的首地址。
其中,typeof是GNU C对标准C的扩展,它的作用是根据变量获取变量的类型。
下面先看第一句:const typeof( ((type *)0)->member ) *__mptr = (ptr);
- 首先把0强制转换为type*型的指针;
- 然后取指针指向的type类型结构体中的member;那么这里(type )0)岂不是(type )NULL,不可以对空指针引用啊。那么我的个人理解是NULL指针不能被解引用(NULL)而不是NULL指针不能使用,这里 ((type )NULL)->member只是用空指针和空指针指向的type结构体获得member成员,而并没有直接去读null地址的数据,换句话说这里只是使用 了NULL但并未进行 (*NULL)操作。
- 接着用typeof得到上述member成员的类型;
- 最后把ptr赋值给__mptr,__mptr是指向member类型的一个指针。例如member是int类型,那么上述语句就等价于const int * __mptr = (ptr)语句,typeof( ((type *)0)->member )是获得member成员的类型。
接着来看第二句,offsetof的源码定义在stddef.h文件中,如下:
#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
其中__compiler_offsetof的定义在compiler_gcc4.h文件中:
#define __compiler_offsetof(a,b) __builtin_offsetof(a,b)
__builtin_offsetof(a,b)是GCC的内置函数,可认为它的实现与((size_t) &((TYPE )0)->MEMBER)这段代码是一致的。那么((size_t) &((TYPE )0)->MEMBER)这句具体什么意思呢?
1. 首先把0强制转换为type*型的指针;
2. 然后取指针指向的type类型结构体中的member成员;
3. 接着用取地址符号&取member成员的地址,这个地址的值就是offset值;
4. 最后把menber地址强制转换成(size_t)类型。
offset值如下图:
当type类型结构体的首地址为0,那么member成员的地址就是offset值。member的地址减去这个offset值就可得到type结构体首地址。
所以(type )( (char )__mptr - offsetof(type,member) )的含义就是 (char )__mptr 减去offset值得到结构体首地址,并强制转化为(type )型。这里为什么要先把__mptr转化成(char *)是因为如果__mptr是其他类型,比如是int型,用__mptr 直接减 offsetof(type,member)实际上是减了4 X offsetof(type,member)个字节了,而不是减去 offsetof(type,member)个字节,就会引起错误。
实例测试
#include <stdio.h>
struct test_struct {
int num;
char ch;
float f;
};
int main(void)
{
printf("offsetof(struct test_struct, num) = %d\n",
offsetof(struct test_struct, num));
printf("offsetof(struct test_struct, ch) = %d\n",
offsetof(struct test_struct, ch));
printf("offsetof(struct test_struct, f) = %d\n",
offsetof(struct test_struct, f));
return 0;
}
输出;
offsetof(struct test_struct, num) = 0
offsetof(struct test_struct, ch) = 4
offsetof(struct test_struct, f) = 8