对操作系统进行代码实战中,container_of 宏可以说是较先接触的基础知识——通过结构体成员的地址得到结构体的地址。

我最初学习它的时候,学的是 Linux 内核版本的,可在 Visual Studio 敲的时候,竟然报错(发现是编译器兼容问题),在解决这个问题时,我又探索出两种写法,下面来看看这 3 种。

一、原版,工程级严谨

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

ptr 是结构体成员地址,type 是结构体类型(比如 struct task_struct),member 是结构体成员在结构体里的名字,它在编译期进行安全类型检查。

· typeof(((type*)0)->member):编译器在编译期“推导”出 type 结构体中 member 成员的真实类型。

· 把 _mptr 声明为“指向该成员类型的 const 指针”。

· 用 (ptr) 初始化:若 ptr 的类型与 member 实际类型不匹配,编译器会告警甚至报错

这份代码需 GNU 扩展,兼容 GCC 编译器但不兼容 MSVC 编译器,而我们 Windows 用的 Visual Studio 的编译器就是 MSVC,所以报错,这对想写测试程序的人来说真不友好,而且报错的“红”还碍眼。别担心,那我们用下一种写法。

二、简化版,学习管够

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

typeof 咱不要了,我们需要接受的缺点是不能在编译器校验安全,传错成员指针静默算错,我们只能自己留个心眼,但与第一种相比,我们能更清晰地读,对学习完全没问题,而且这种格式在几乎所有 C89/C99 编译器都能过。

三、中间变量版,知道就行

第三种我在解决问题期间遇到过,也讲一下

#define container_of(ptr,type,member)({ \
    void* _mptr=(void*)(ptr); \
    (type*)((char*)_mptr-offsetof(type,member)); \
})

这种健壮性比第二种强点,先把 (void*)(ptr) 存到 _mptr,再用 _mptr 参与运算,为后续宏扩展(比如多次用 _mptr)留出余地,而且调试友好,_mptr 是一个真实存在的局部变量,调试器中可以观察到中间值;第二种是临时强转,没有可观测中间状态。

但是我的副标题写着“知道就行”,其实是这种写法太鸡肋。

① 依旧没有 typeof,所以不能在编译期进行类型安全检查,传错指针静默算错,健壮性没强哪去;

②({...})写法是GCC扩展,MSVC仍不支持,可移植性差。

所以没必要再费我们的脑容量去记它。

如果读者们有好想法或发现,欢迎分享;文章内容有不严谨甚至错误信息,欢迎指出;对问题有不理解的,欢迎提问,良好的学术氛围离不开大家的努力与热情。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐