怀疑linux中的container_of宏

为什么我们使用container_of宏?

 container_of(pointer, container_type, container_field); 

据LDD说

“这个宏在一个类型为container_field的结构中接受一个名为container_field的字段的指针,并返回一个指向包含结构的指针”。

我的问题是:

  • 如果我们想要一个指向结构的指针(即container_type),我们可以直接分配,对吧?
  • 那为什么其中一个字段的指针被用来为整个结构分配地址?
  • 任何人都可以用一个例子解释使用该宏的优势吗?

让我给你举个例子:

 struct A { int some_data; int other_data; }; 

现在让我们说有这个function:

 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 这里的关键是我们没有对结构本身的引用,而只是指向其成员之一的指针

这看起来很荒谬,但实际上在一些非常聪明的结构中很有用。 一个非常常见的例子是内核创建链表的方式。 我建议你在kernelnewbies.org上阅读这篇文章 。 让我们看一个简短的例子:

 struct whatever { /* whatever data */ struct list_head mylist; }; 

所以struct whatever具有一些数据的东西,但它也希望充当链表中的节点。 他们在学校教你的是拥有一个不同的结构,包含next / prev指针以及指向struct whatever (或void * )的指针。 这样,您就拥有了通过其获取数据的节点。

按照所有软件工程标准,这实际上是一件好事。 但软件工程标准对效率的要求很低。 请参阅为什么我应该用C编写ZeroMQ,而不是C ++(第二部分) 。

底线是,使用传统方法,您必须与数据节点分开分配链接列表节点,即您将内存分配,解除分配,碎片和缓存未命中等的开销加倍。 Linux内核的方式恰恰相反。 每个数据节点都包含一个通用链表节点。 通用链表节点不知道有关数据或它们如何分配的任何信息,只知道如何连接到其他链表节点。

让我们深入了解一下:

  +-------------------+ +---------------------+ +---------------------+ | | | | | | | WHATEVER DATA | | WHATEVER DATA 2 | | WHATEVER DATA 3 | | | | | | | | | | | | | | | | | | | | | | | | | +-------------------+ +---------------------+ +---------------------+ | |----->| |----->| | | mylist | | mylist 2 | | mylist 3 | | |<-----| |<-----| | +-------------------+ +---------------------+ +---------------------+ 

你所拥有的链表是struct list_head中指向其他struct list_head的指针。 请注意,它们并不指向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

 Why do we use container_of macro ? 

容器的宏用于获取指向包含元素的结构的开头的指针。

例如

 struct container { int some_other_data; int this_data; } 

并且指针int *my_ptr指向this_data成员,您将使用宏来获取指向struct container *my_container的指针:

 struct container *my_container; my_container = container_of(my_ptr, struct container, this_data); 

this_data的偏移量考虑到结构的开头对于获取正确的指针位置至关重要。

实际上,您只需从指针my_ptr减去成员this_data的偏移量即可获得正确的位置。

另见这里清楚你的疑惑。

是的,您可以使用自己编写的演员/作业,但它有一些优点。

效率 – 使用宏而不是声明整个演员表并不容易吗?

维护 – 如果每个人都使用宏,只需编辑宏,就可以在整个项目中轻松更改指针分配的方式。

可读性 – 什么是易读的? 一个明确的演员或宏为你做的?

安全性 – 您可能相信此宏可以工作,并且它运行任何重要的强制转换和类型检查,比您自己的代码更好。