container_of
链表是内核最经典的数据结构之一,说到链表就不得不提及内核最经典(没有之一)的宏container_of。
container_of似乎就是为链表而生的,它的主要作用是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针,最典型的应用就是根据链表节点获取链表上的元素对象。
container_of的宏定义如下:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
宏里面包含了两个关键字:typeof和offsetof
typeof
typeof是GNU对C新增的一个扩展关键字,用于获取一个对象的类型,在很多时候我们处理的对象通常是一个指针,而此时如果想知道指针所指向的对象的类型,typeof就派上用场了,详见GNU的官方文档 ,现在看container_of宏的第一条语句:
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
创建一个类型为const typeof( ((type )0)->member ) ,即类型为type结构的member域所对应的对象类型的常指针__mptr,并用ptr初始化之,这样一来,__mptr就指向了某一个type的member域。因为数据结构是顺序存储的,此时如果知道member在type结构中的相对偏移,那么用__mptr减去此偏移便是ptr所属的type的地址,因此宏的第二条语句应运而生:
(type *)( (char *)__mptr - offsetof(type,member) );})
offsetof
offsetof作用是返回一个数据域在它所属的数据结构中的相对偏移,单位是size_t,宏定义如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
将地址0强制转换为type*,那么0指向某一个type类型的对象,也就是此type对象的地址,那么(TYPE*)0)->MEMBER就是type的member域,现在取址后&((TYPE *)0)->MEMBER 就是member域的地址,因为type的地址为0,则member的地址实质上就是它相对于type地址的偏移。这里为什么可以这样实现而不会出错呢?有两点原因,其一,地址0是在编译器编译时已经指定好了的,其二,这里全部都是取址操作,并没内存数据访问,因此不会存在非法访问内存的问题。