前言
看内核源码时经常遇到这个宏,现在解释一下这个,记录备查。
第一张图说明了container_of(ptr, type, member)
宏的原理
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
表达式中的语句
引起您注意的第一件事可能是整个表达式的结构。该语句应返回一个指针,对吗?但是,其中只有一种奇怪的({})块,其中有两个语句。实际上,这是对C语言的GNU扩展,称为表达式内的括号组。编译器将评估整个块并使用该块中包含的最后一条语句的值。以下面的代码为例。它将打印5。
int x = ({1; 2;}) + 3;
printf("%d\n", x);
typeof()
这是非标准的GNU C扩展。它接受一个参数并返回其类型。它的确切语义在gcc文档中进行了全面介绍。
int x = 5;
typeof(x) y = 6;
printf("%d %d\n", x, y);
零指针取消引用
但是零指针取消引用呢?好了,获取成员的类型有点魔力。它不会崩溃,因为表达式本身永远不会被求值。编译器只关心其类型。万一我们要求提供地址,也会发生同样的情况。编译器再次不在乎该值,它只是将成员的偏移量添加到结构的地址(在这种情况下为0),然后返回新地址。
struct s {
char m1;
char m2;
};
/* This will print 1 */
printf("%d\n", &((struct s*)0)->m2);
另请注意,以下两个定义是等效的:
typeof(((struct s *)0)->m2) c;
char c;
offsetof(st, m)
该宏将成员的字节偏移量返回到结构的开头。它甚至是标准库的一部分(可在stddef.h中使用)。但是不在内核空间中,因为那里没有标准的C库。就像我们之前看到的那样,它有点像0指针取消引用魔术,并且为了避免现代编译器通常提供实现该功能的内置函数。这是凌乱的版本(来自内核):
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
它返回一个类型为TYPE的结构的成员MEMBER的地址,该地址从地址0(恰好是我们要查找的偏移量)存储在内存中。
放在一起
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
当您从这篇文章的开头更加仔细地查看原始定义时,您会开始怀疑第一行是否真的适合任何事情。你会是对的。第一行对于宏的结果并不是本质上重要的,但是它用于类型检查。第二行真正的作用是什么?它从其地址减去结构成员的偏移量,从而得出容器结构的地址。而已!