在Linux内核源码中,实现和链表相关的接口list_entry()时,会调用container_of()宏定义,它的作用是:给定结构体中某个成员的地址、该结构体类型和该成员的名字,来获取这个成员所在的结构体变量的首地址。有点绕,没关系,接着往下看就能明白了。
container_of()宏定义实现如下所示/**
* 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) );})
要看懂上述代码,需要知道三个关键点:typeof(),取变量或表达式类型,可参考之前typeof的文章
typeof( ((type *)0)->member ),用typeof( ((struct apple *)0)->color ) 解释更易理解,它的作用是获取结构体apple中变量color的类型。获取结构体中某个成员相对于该结构体首元素地址的偏移量,可参考之前的文章。
好了,再开始解释container_of(),其三个入参含义:ptr:结构体变量中某个成员的地址
type:结构体类型
member:该结构体变量的具体名字
比如如下结构体struct ipstore,假设已经有一个变量struct ipstore *ist1;,并给ist1分配好了内存且进行了初始化,在已知结构体成员list的地址的情况下,获取list所在的结构体变量ist1的首地址。此时有人就会问了,这里ist1明明是已知的,这么做不是自己给自己找麻烦吗?这是个好问题,但是不要忘记,本文到目前为止只是讲解container_of()的含义,并没有说它适合用在什么样的场景下,因为有一种使用场景,当链表是通过list串起来的时候,此时并不知道ist1的首地址,反而是知道list的地址,这时container_of()就非常合适了,内核中的链表就是这么做的。struct list_head {
struct list_head *next;
struct list_head *prev;
};struct ipstore{
unsigned long time;
__u32 addr[4];
struct list_head list;
};
所以,调用container_of()时具体的传参如下所示,其返回的结果是ist1的地址。container_of(ist1->list, struct ipstore, list)
如下测试代码,通过container_of()取得了ip1的地址。void container_of_test()
{
struct ipstore ip1;
struct ipstore *p1;p1 = container_of(&ip1.list, struct ipstore, list);
printf("ip1's addr:0x%0x\n", &ip1);
printf("p1's addr:0x%0x\n", p1);
}