让我举个例子:
struct A
{
int some_data;
int other_data;
};
现在假设有这个函数:
int get_some_data_from_other(int *other)
{
struct A *a = container_of(other, struct A, other_data);
return a->some_data;
}
如您所见,我们可以通过知道指针应该指向的结构的哪个字段来判断包含给定int *other 的原始struct A 是什么。 这里的关键是我们没有对结构本身的引用,而只是指向其成员之一的指针。
这可能看起来很荒谬,但实际上在一些非常聪明的构造中很有用。一个非常常见的例子是内核创建链表的方式。我建议你阅读this post on kernelnewbies.org。让我们看一个简短的例子:
struct whatever
{
/* whatever data */
struct list_head mylist;
};
所以struct whatever 有一些数据,但它也想充当链表中的节点。他们在学校教你的是有一个不同的结构,它包含next/prev 指针以及指向struct whatever(或void *)的指针。这样,您就有了可以访问数据的节点。
按照所有软件工程标准,这实际上是一件好事。但是软件工程标准很少考虑效率。见Why should I have written ZeroMQ in C, not C++ (part II)。
底线是,使用传统方法,您必须将链表节点与数据节点分开分配,即您将内存分配、释放、碎片和缓存未命中等开销加倍。 Linux 内核的做法恰恰相反。每个数据节点都包含一个通用链表节点。通用链表节点对数据或它们如何分配一无所知,只知道如何连接到其他链表节点。
那么让我们深入了解一下:
+-------------------+ +---------------------+ +---------------------+
| | | | | |
| WHATEVER DATA | | WHATEVER DATA 2 | | WHATEVER DATA 3 |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
+-------------------+ +---------------------+ +---------------------+
| |----->| |----->| |
| mylist | | mylist 2 | | mylist 3 |
| |<-----| |<-----| |
+-------------------+ +---------------------+ +---------------------+
您所拥有的链表是struct list_head 内的指针,它们指向其他struct list_heads。请注意,它们不指向struct whatever,而是指向mylist在这些结构中。
假设您有一个节点struct whatever w。你想找到下一个节点。你能做什么?首先,您可以使用w.mylist.next 来获取指向下一个节点的mylist 的指针。现在您必须能够提取包含该节点的实际struct whatever。这就是使用container_of 的地方:
struct whatever w_next = container_of(w.mylist.next, struct whatever, mylist);
最后,请注意,Linux 内核具有遍历链表所有节点的宏,这通常不是您想要的,因此您实际上不需要自己直接使用container_of。