18

在查看 Linux 内核对双向循环列表的实现时,我发现了以下宏:

#define container_of(ptr, type, member) ({           \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})

它的工作方式是它返回指向结构的指针,该结构仅给出其成员之一的地址:

struct blabla
{
    int value;
    struct list_head *list;
}

因此,仅给出指向列表的指针,您就可以获得指向 blabla 的指针(并获得“值”)。对于我的问题,我将如何使其尽可能便携(符合 C89/C99 的最佳情况?)。由于使用了 typeof(),这只是 gcc。

这是我到目前为止所得到的:

#define container_of(ptr, type, member) (                  \
                (type *) (char *)(ptr)-offsetof(type,member)\
                )

这个片段是否符合 ISO 标准(因此应该能够在任何符合标准的编译器上编译)?

4

4 回答 4

20

正如 Ouah 评论的那样,({ ... })语句表达式是 GNU 扩展;你将无法使用它。您的核心表达式接近所需内容,但没有足够的括号:

#define container_of(ptr, type, member) \
                      ((type *) ((char *)(ptr) - offsetof(type, member)))

这对我来说看起来很干净。对于 SO,它只分布在两条线上。

于 2012-04-22T16:28:10.003 回答
15

宏的编写方式与执行类型检查的方式相同ptr__typeof__如果编译器与 gcc 不兼容,则可以使用复合文字而不是语句表达式并回退到简单的指针检查而不是使用:

#ifdef __GNUC__
#define member_type(type, member) __typeof__ (((type *)0)->member)
#else
#define member_type(type, member) const void
#endif

#define container_of(ptr, type, member) ((type *)( \
    (char *)(member_type(type, member) *){ ptr } - offsetof(type, member)))
于 2012-04-22T16:48:23.640 回答
3

带类型检查的 ISO C90 兼容版本。(但是,警告:对 ! 的两个评估ptr

#define container_of(ptr, type, member) \
   ((type *) ((char *) (ptr) - offsetof(type, member) + \
              (&((type *) 0)->member == (ptr)) * 0))

struct container {
  int dummy;
  int memb;
};


#include <stddef.h>
#include <stdio.h>

int main()
{
  struct container c;
  int *p = &c.memb;
  double *q = (double *) p;
  struct container *pc = container_of(p, struct container, memb);
  struct container *qc = container_of(q, struct container, memb);
  return 0;
}

测试:

$ gcc -Wall containerof.c
containerof.c: In function ‘main’:
containerof.c:20:26: warning: comparison of distinct pointer types lacks a cast
containerof.c:20:21: warning: unused variable ‘qc’
containerof.c:19:21: warning: unused variable ‘pc’

我们收到distinct pointer types26 的警告,但不是 25。这是我们对指针被滥用的诊断。

我首先尝试将类型检查放在逗号运算符的左侧,gcc 抱怨没有效果,这很麻烦。但是通过使其成为操作数,我们确保它被使用。

ISO C 没有很好地定义这个&((type *) 0)->member技巧,但它被广泛用于定义offsetof. 如果你的编译器使用这个空指针技巧offsetof,它几乎肯定会在你自己的宏中表现自己。

于 2012-04-24T02:12:35.540 回答
0

是的,您可以使“container_of”宏严格符合 ISO C。为此,您需要两件事:

  1. 摆脱 GNU 扩展;

  2. 找到一种检查类型兼容性的方法。

基本上,类型检查不是运行时操作,而是编译时。而且我没有看到任何原因,为什么原始的“container_of”实现创建新变量只是为了分配它并执行类型检查。无需在某些表达式中创建新变量即可完成此操作,该表达式仅在编译时计算(并检查类型)。幸运的是,我们在 C 中没有太多选择,唯一的选择是使用“sizeof(expression)”来检查类型。看一个例子:

#define container_of(ptr, type, member) \
    ( (void)sizeof(0 ? (ptr) : &((type *)0)->member), \
      (type *)((char*)(ptr) - offsetof(type, member)) )

在第一行检查类型兼容性(三元运算符编译器必须确保类型可能转换为通用类型,或者两种类型兼容)。第二行与原始“container_of”宏相同。

您可以在 GodBolt ( https://godbolt.org/z/MncvzWfYn ) 上使用测试程序,并确保这个符合 ISO 标准的变体即使在 Microsoft 的 Visual Studio 编译器中也能正常工作。

于 2021-12-11T19:22:59.197 回答