在查看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;
}
因此,只有指向list的指针,才能获得指向blabla(并获得“value”)的指针。 对于我的问题,我如何使其尽可能便携(符合C89 / C99的最佳情况?)。由于使用了typeof(),这只是gcc。
这是我到目前为止所得到的:
#define container_of(ptr, type, member) ( \
(type *) (char *)(ptr)-offsetof(type,member)\
)
此代码段是否符合ISO标准(因此应该可以在任何符合标准的编译器上编译)?
答案 0 :(得分:16)
正如Ouah评论的那样,({ ... })
语句表达式是GNU扩展;你将无法使用它。您的核心表达式接近所需,但没有足够的括号:
#define container_of(ptr, type, member) \
((type *) ((char *)(ptr) - offsetof(type, member)))
这看起来很干净。它只分散在两条线上。
答案 1 :(得分:12)
宏的编写方式与对ptr
进行类型检查的方式相同。如果编译器不兼容gcc,则可以使用复合文字而不是语句表达式,并回退到指针的简单检查,而不是使用__typeof__
:
#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)))
答案 2 :(得分: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’
我们得到26的distinct pointer types
警告,但不是25.这是我们对指针被误用的诊断。
我首先尝试将类型检查放在逗号运算符的左侧,gcc抱怨它没有效果,这是一个麻烦。但是通过使其成为操作数,我们确保使用它。
ISO {C}没有很好地定义&((type *) 0)->member
技巧,但它被广泛用于定义offsetof
。如果你的编译器对offsetof
使用这个空指针技巧,它几乎肯定会在你自己的宏中表现出来。