【问题标题】:Doubts regarding container_of macro in linux关于linux中container_of宏的疑问
【发布时间】:2014-03-28 11:32:25
【问题描述】:

我们为什么要使用container_of 宏?

container_of(pointer, container_type, container_field);

在LDD中说

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

我的问题是:

  • 如果我们想要一个指向结构的指针(即 container_type),我们可以 直接赋值对吧?
  • 那么为什么它的一个字段的指针被用来分配一个 整个结构的地址?
  • 谁能举例说明使用的好处 那个宏?

【问题讨论】:

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

标签: c macros linux-kernel linux-device-driver embedded-linux


【解决方案1】:

让我举个例子:

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

【讨论】:

    【解决方案2】:
    Why do we use container_of macro ?
    

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

    例如

    struct container {
      int some_other_data;
      int this_data;
    }
    

    还有一个指针 int *my_ptr 指向 this_data 成员,您可以使用宏来获取指向结构容器 *my_container 的指针,方法是:

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

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

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

    另请参阅here,它可以消除您的疑虑。

    【讨论】:

    • 'struct container *my_container' 声明本身会隐式指向结构的开头,对吧?那为什么要显式声明呢?
    • @Dino struct container *my_container 未初始化。它没有指向任何地方。我们所拥有的只是一个指向int 的指针。如果我们知道 int 是 struct container 的成员,我们可以恢复指向 struct container 的指针。
    【解决方案3】:

    是的,您可以为它编写自己的演员表/作业,但它确实有一些优势。

    效率 - 只使用宏而不是声明整个演员阵容不是更容易吗?

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

    可读性 - 什么是容易阅读的?显式转换或为您执行此操作的宏?

    安全性 - 您可以相信此宏有效,并且它运行任何重要的强制转换和类型检查,比您自己的代码要好得多。

    【讨论】:

      猜你喜欢
      • 2013-03-27
      • 2019-02-11
      • 1970-01-01
      • 2011-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-02
      相关资源
      最近更新 更多