宏 container_of在linux kernel内核中被广泛应用,它的作用是通过结构体成员的地址和结构体成员的名字以及结构体类型获取结构体成员所在结构体的首地址,有点绕。
其定义为
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
其中ptr为结构体成员的地址,type为结构体类型,member为结构体成员的名字(不是变量名,是变量前的类型的名字)。
它经历了两个步骤:
第一:
const typeof( ((type *)0)->member ) *__mptr = (ptr);
这条语句声明了一个临时变量__mptr,它的类型应该是member的类型,但是我们只有member的名字而没有member的类型,因此我们需要根据member名字反推它的类型。
这里使用到了一个“虚拟指针”---0,将0强制转化为type*类型,这样0地址后面的一段区域就被认为包含了type的元素,因此就可以通过->符号来得到其中的成员member,再通过typeof()获取其类型即可。(我认为这里的“虚拟指针”---0不一定必须是0,也可以是1,2,3等其他值,因为它只是临时充当了一下索引介质。) 这样,我们就获得了一个类型与ptr指向变量的类型相同的指针__mpter,而它的值与ptr一样,因此,__mptr实际就已经被连接到了我们的目标结构体了。
第二:
(type *)( (char *)__mptr - offsetof(type,member) );})
使用__mpter减去一个offset值,而这个offset值就是__mptr所属的结构体成员在结构体内部的偏移地址,这样减法的结果就是找到了所在结构体的首地址。
而offsetof(type,member)又是什么呢?其实现为
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
它也用到了(TYPE*)0这个东西,通过将0地址转化为(TYPE*)再索引到结构体成员MEMBER,再对该成员取地址就得到了该结构体成员的地址,而由于我们结构体的首地址为---0,所以此时MEMBER的地址实际就是结构体成员MEMBER在结构体内的偏移量。