很多人第一次看到 Linux 内核代码时都会被这个宏震撼到container_of(ptr, type, member)甚至第一次看到源码#define container_of(ptr, type, member) ({ const typeof(((type *)0)-member) *__mptr (ptr); (type *)((char *)__mptr - offsetof(type, member)); })大脑基本是这样的很多人甚至会觉得这是黑魔法这是宏体操这是 Linux 炫技但实际上container_of 是 Linux 内核最核心的对象模型之一甚至可以说不理解 container_of就不理解 Linux Kernel 的设计思想。一、先别急着看宏我们先看一个问题。假设typedef struct { int id; int age; } Student;现在Student stu;我们知道stu.age可以拿到成员地址但问题来了如果我只有成员地址stu.age能不能反推出整个 stu 对象地址答案可以。而这就是 container_of 的本质。二、为什么能反推因为struct 内存是连续布局的。比如typedef struct { int id; int age; } Student;内存Student: ------- | id | offset 0 ------- | age | offset 4 -------如果age 地址 0x1004而age offset 4那么对象地址 成员地址 - offset也就是0x1004 - 4 0x1000于是就找回了整个对象。三、offsetof 到底是什么container_of 核心依赖offsetof(type, member)很多人其实也没真正理解它。来看offsetof(Student, age)结果4因为age 距离结构体起始地址偏移4字节offsetof 本质“计算成员在结构体中的偏移量”四、offsetof 是怎么做到的经典实现#define offsetof(type, member) \ ((size_t)(((type *)0)-member))第一次看又开始黑魔法了...我们拆开看。第一步(type *)0意思把 0 当成 type*也就是假装对象从地址0开始第二步((type *)0)-member意思取 member但不会真的访问内存。这里只是编译期计算偏移。第三步(((type *)0)-member)得到member 的地址而因为基地址是0所以member地址 offset五、终于来到 container_of现在container_of(ptr, type, member)就容易理解了。本质对象地址 成员地址 - 成员偏移Linux 内核实现简化版#define container_of(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member)))拆开ptr成员地址。offsetof(type, member)成员偏移。ptr - offset得到整个对象地址六、为什么 Linux 内核特别喜欢 container_of因为Linux 内核极度依赖“struct embedding”也就是“组合模拟继承”比如struct device { ... }; struct usb_device { struct device dev; ... };看到没usb_device 包含 device这其实就是C 语言版继承。但问题来了如果device*传来怎么找到真正 usb_device*答案container_of。七、Linux 内核经典用法比如struct list_head { struct list_head *next, *prev; };Linux 链表根本不存数据。而是把链表节点嵌入对象。例如struct student { int id; struct list_head list; };于是student 里面嵌了 list 节点遍历链表时拿到的是list_head*但真正需要student*怎么办答案container_of。八、现在你会突然理解 Linux 的设计哲学Linux 不喜欢对象管理链表而是“对象自己携带链表节点”这叫intrusive list侵入式链表优势巨大优势原因无额外节点内存节点嵌入对象性能极高少一次内存分配cache友好数据连续泛型能力强container_of反推对象九、container_of 本质是什么很多人以为container_of 是宏技巧。其实不是。它真正本质“通过成员反推对象”也就是成员知道自己属于谁这其实已经非常像面向对象思想。十、现在再回头看你会发现Java编译器帮你维护对象关系C编译器帮你维护vptr vtable 继承布局Linux Kernel则是工程师自己维护对象模型。于是struct embedding container_of共同构成Linux Kernel 的对象系统。十一、一句话总结offsetof计算成员偏移。container_of通过成员地址反推整个对象。而struct embedding container_of本质其实是Linux Kernel 的“继承 对象模型”。十二、最后现在再回头看 Linux 内核你会发现它虽然写的是C但背后其实一直在“手写面向对象系统”。所以Linux Kernel 真正厉害的地方从来不是宏黑魔法而是“用 C 构建完整对象模型的能力”。系列总结第一篇总纲篇《为什么 Linux / Android 系统里全是 struct 函数指针》第二篇多态篇《从函数指针到虚函数表彻底理解 C 的多态》第三篇JNI篇《JNIEnv 为什么是二级指针本质就是函数表》第四篇本文《Linux 内核里的 container_of 到底是什么黑魔法》至此C对象模型 ↓ 函数指针 ↓ 虚函数表 ↓ JNI函数表 ↓ Linux对象系统这一整条系统层主线终于彻底串起来了。