C语言对象模型系列(四)《Linux 内核里的 container_of 到底是什么黑魔法?》—— 一篇讲透 Linux 内核的“对象模型”核心技巧

很多人第一次看到 Linux 内核代码时,

都会被这个宏震撼到:

cpp 复制代码
container_of(ptr, type, member)

甚至第一次看到源码:

cpp 复制代码
#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对象系统

这一整条系统层主线,

终于彻底串起来了。

相关推荐
SWAGGY..1 小时前
Linux系统编程:(二)基础指令详解
linux·运维·服务器
AI_Ming2 小时前
从0开始学AI:层归一化,原来是这回事!
算法·ai编程
kdxiaojie2 小时前
U-Boot分析【学习笔记】(3)
linux·笔记·学习
WL_Aurora2 小时前
备战蓝桥杯国赛【Day 8】
算法·蓝桥杯
烛衔溟2 小时前
TypeScript 接口继承与混合类型
linux·ubuntu·typescript
2501_931803752 小时前
Go:一门为解决C语言痛点而生的现代语言
c语言·开发语言·golang
智者知已应修善业2 小时前
【51单片机模拟生日蜡烛】2023-10-10
c++·经验分享·笔记·算法·51单片机
MediaTea2 小时前
Scikit-learn:从数据到结构——无监督学习的最小闭环
人工智能·学习·算法·机器学习·scikit-learn
智者知已应修善业2 小时前
【51单片机如何让LED灯从一亮到八,再从八亮到一】2023-10-13
c++·经验分享·笔记·算法·51单片机