从成员指针反推出父结构体——container_of
2026/9/6大约 2 分钟
从成员指针反推出父结构体——container_of
一、问题
假设有一个结构体:
struct Task
{
int id;
int priority;
struct ListNode node;
};现在我们只有:
struct ListNode *node;但真正想要的是:
struct Task *task;怎么办?
核心只有一句:
父结构体地址 = 成员地址 - 成员偏移量二、先理解结构体内存布局
假设:
Task起始地址 = 1000
node偏移量 = 8那么:
Task
┌──────────────┐ 1000
│ id │
├──────────────┤
│ priority │
├──────────────┤ 1008
│ node │
└──────────────┘
↑
node所以:
node地址 = Task地址 + node偏移量反过来:
Task地址 = node地址 - node偏移量这就是 container_of 的核心。
三、这个奇怪的表达式是什么?
&((parent *)0)->name它的作用不是访问地址 0。
而是:
计算
name成员在parent结构体中的偏移量。
例如:
struct Task
{
int id; // 0
int priority; // 4
struct ListNode node; // 8
};那么:
&((struct Task *)0)->node得到的就是:
8也就是:
node 的偏移量四、整个宏就非常简单了
#define tNodeParent(node, parent, name) \
(parent *)((uint32_t)node - \
(uint32_t)&((parent *)0)->name)拆开:
node
↓
成员的实际地址减去:
&((parent *)0)->name
↓
成员偏移量得到:
父结构体起始地址最后转换成:
(parent *)于是:
成员指针
↓
减去成员偏移
↓
父结构体指针五、完整例子
struct Task
{
int id;
int priority;
struct ListNode node;
};已知:
struct ListNode *p;调用:
struct Task *task =
tNodeParent(p, struct Task, node);本质就是:
Task地址
=
node地址
-
offsetof(struct Task, node)这就是标准的 container_of 思想。
六、一句话记住
已知成员地址
↓
减去成员在结构体中的偏移
↓
得到整个结构体地址即:
container = member - offsetof(container, member);container_of 的本质:通过成员地址,找回它所属的父结构体。
这也是 Linux 内核、RTOS、嵌入式链表实现中非常经典的技巧。

