最近在看linux驱动时偶然间发现了这两个宏函数offsetof、container_of,刚开始看的时候一头雾水,后来网上找了些资料理解了下大概清楚了它的整个过程,下面我来解析一下
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) (type *)( (char *)ptr - offsetof(type,member) )
作用:offsetof()函数主要用来计算member成员相对于结构体起始地址的偏移量。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
TYPE : 一种结构体类型
MEMBER: TYPE结构体内部的成员变量名
size_t : 相当于 无符号整型unsigned int
具体意义 : 将 0 地址强制转换为一个TYPE类型的结构体指针,这是完全没有问题的,根据这个结构体指针使用->符号指向这个结构体其中的MEMBER成员,然后利用 & 取出MEMBER成员相对于 0(TYPE结构体首地址) 的地址,那么其实最后offsetof()的返回值就是MEMBER成员相对于结构体首地址的偏移量,为了让结果是以字节为单位,我们将结果强制转换为size_t类型。
作用:container_of()函数主要用来计算结构体的首地址
#define container_of(ptr, type, member) (type *)( (char *)ptr - offsetof(type,member) )
ptr : 是当前member成员所在的地址
具体意义 : 首先说明为什么要将 ptr 强转成为指向char类型的指针,因为我们offsetof()函数的返回值是以一个字节为单位,如果我们不强转成指向char类型的指针的话,假如ptr是一个指向int类型的指针,它每偏移一次就相当于偏移了四个字节,那我们offsetof()函数返回的又是以一个字节为单位,如果是这样的话,那我们最后得到的结果可能是偏移了原本的结果的四倍,所以必须将ptr强制转换为指向char类型的指针。最后我们再解释一下这个函数具体的意义,通过offsetof()函数返回的偏移量,根据member成员已知的当前地址,减去member成员相对于结构体首地址的偏移量,最终的差值就是结构体现在的首地址,然后最后将地址强制转换为type类型的结构体指针。
看一段跟上面差不多的宏函数,只不过这个更严谨一些
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
上面解释了offsetof()函数的功能,所以这里说下这段代码它的严谨性在哪里,其他的跟之前是一样的。
看这个第二行代码 :
const typeof( ((type *)0)->member ) *__mptr = (ptr);
最开始我有点感到奇怪,为什么还要单独定义一个__mptr指针变量用来接收ptr的指针值,后来才发现这里其实是想判断通过member成员变量定义的一个指针是否与ptr所指向的类型一致,其实ptr就是告诉我们已知的member成员的地址,这样写就比上面的写法更严谨了,这样还能判断一下这个地址(ptr)指向的类型是否与member现在定义的这个指针指向的类型是否一致,如果不一致的话,编译器会有警告提示的。
总之最后想说的就是,平时我们用的多的可能都是根据结构体的首地址得出成员变量,比如
struct Exmple* exmple;
exmple->a = 1;
exmple->b = 2;
exmple->c = 3;
但是这两个宏函数就恰恰相反,根据结构体中的一个成员变量的地址得出结构体的首地址。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) (type *)( (char *)ptr - offsetof(type,member) )
好了,今天分享到此结束!祝大家阅读愉快…