0 、前言
前段时间在做算法移植的时候遇到一个问题,源头直指一个名为container_of()的函数,该函数的花式写法也着实让人不禁多看几眼。该宏的定义如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({
\
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
刚看到这段代码的时候,我也是这是啥啊?怀疑人生啊!
1、container_of()的实现
上述宏的作用呢,是:通过struct结构体中的某个成员的指针(地址),从而获取指向struct结构体起始地址的指针。其实现主要的关键点如下面所述:
- typeof关键字的使用;
- 0地址指针的使用;
- offsetof()如何获取传入的结构体成员的偏移地址?
现在就从上述三个关键点开始来看看这宏是怎么实现其的花式操作的。
1.1、typeof关键字
乍一看,typeof和常用的sizeof关键字有几分神似,sizeof是C标准库中所定义的关键字,用于获取变量和数据类型在内存中所占的存储字节数。正如sizeof其名为获取对象的size,typeof的作用就是获取对象的type即类型。typeof非定义在C标准库中,而是GNU C的拓展,关于typeof关键字的介绍详细的可由此传送门至官网介绍6.7 Referring to a Type with typeof。
1.2、零值指针
零值指针顾名思义就是值为0的指针,即地址为0,同时其可以是任何类型的指针,可以是void*,char*,int*等等。
1.3、offset()获取偏移地址
offset()获取偏移地址就是巧妙地应用了0地址指针,将0指针强制转化为所指定的type类型,再通过其访问结构体内成员,并通过&获取成员的地址。由于该强制转化而来的结构体位于0地址,所以获取的成员的地址即是该成员与结构体起始地址的偏移地址。
1.4、花括号表达式
假如是初次遇到,看到宏里面的一对大括号包裹起来的表达式或许会觉得有些诧异,其实这也是gnu的另一个拓展,正式名称为为:braced-group within expression,花括号里表达式的值为最后一条语句的值。
1.5、container_of()
通过上述三点的叙述container_of()是怎么完成花式操作的基本上就明了了。首先说说container_of(ptr, type, member)入参的三个量分别是什么,如下表述。
参数 | 描述 |
---|---|
ptr | 已知的某成员的地址 |
type | 目标结构体类型 |
member | 已知的结构体成员 |
上述引用的图片也清晰地表明了这个宏的原理。通过offset()宏我们已经获取该member在结构体中的偏移地址;接下来的代码const typeof( ((type *)0)->member ) *__mptr = (ptr);意思是用typeof()获取结构体里member成员属性的类型,然后定义一个该类型的临时指针变量__mptr,并将ptr所指向的member的地址赋给__mptr;最后的代码(type *)( (char *)__mptr - offsetof(type,member) );中,__mptr减去它自身在结构体type里的偏移量就找到了结构体的入口地址,最后将该地址强转成目标结构体的类型,以上完成该花式操作。
2、后记
聊完container_of()的花式操作,我们再看回我和container_of()初遇的不解之缘,编译的Error的log如下:
../test.c:33:23: error: expected declaration specifiers or '...' before '(' token
33 | const typeof( ((type *)0)->member ) *__mptr = (ptr); \
| ^
../test.c:782:17: note: in expansion of macro 'container_of'
../test.c:34:27: error: '__mptr' undeclared (first use in this function)
34 | (type *)( (char *)__mptr - offsetof(type,member) );})
| ^~~~~~
../test.c:34:27: note: each undeclared identifier is reported only once for each function it appears in
34 | (type *)( (char *)__mptr - offsetof(type,member) );})
| ^~~~~~
灵光一现,typeof关键字和上述写法是基于GUN C提供的一种拓展的特性,那么错误的原因基本就定位了,八九不离十是编译标准的问题,将makefile中的-std=c99,更换为-std=gnu99标准,编译通过。
如有错误欢迎指出,谢谢!
参考文章: