🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习 🎬擅长领域:驱动开发,嵌入式软件开发,BSP开发 ❄️作者主页:一个平凡而乐于分享的小比特的个人主页 ✨收录专栏:Linux,本专栏目的在于,记录学习Linux操作系统的总结 欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖

container_of宏详解container_of宏?container_of是Linux内核中一个非常巧妙且常用的宏,它的作用是通过结构体成员的地址反向推导出包含该成员的结构体的地址。
想象一下这样的场景:
container_of就是这个"导航工具"container_of?在Linux内核编程中,经常使用链表来管理各种数据结构。一个典型的设计模式是:
// 示例:内核链表节点结构
struct list_head {
struct list_head *next, *prev;
};
// 业务数据结构
struct my_struct {
int data;
struct list_head list; // 嵌入的链表节点
};问题:当我们遍历链表时,只能拿到list_head的指针,如何获取包含它的my_struct结构体呢?
特性 | 传统方法(直接访问) | container_of方法 |
|---|---|---|
数据组织 | 结构体包含指针指向数据 | 数据包含链表节点 |
内存效率 | 需要额外指针,效率低 | 内存紧凑,效率高 |
代码复杂度 | 简单直观 | 需要宏包装,但复用性高 |
典型场景 | 普通应用编程 | 内核、嵌入式等系统编程 |
可维护性 | 修改时需要同步多处 | 结构体修改影响小 |
container_of的原型与实现#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })或者更简洁的版本:
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))参数说明表格:
┌─────────┬─────────────────────────────────────────┐
│ 参数 │ 说明 │
├─────────┼─────────────────────────────────────────┤
│ ptr │ 结构体成员的指针 │
│ type │ 包含该成员的结构体类型 │
│ member │ 成员在结构体中的名称 │
└─────────┴─────────────────────────────────────────┘内存布局示意图:
┌─────────────────────────────────────────────┐
│ struct container │
│ ┌──────────────────────────────────────┐ │
│ │ 成员1 │ │
│ ├──────────────────────────────────────┤ │
│ │ 成员2 │ │
│ ├──────────────────────────────────────┤ │
│ │ ... │ │
│ ├──────────────────────────────────────┤ │
│ │ member (我们已知它的地址: ptr) │←─┘ 已知点
│ │ │
│ ├──────────────────────────────────────┤
│ │ 其他成员... │
│ └──────────────────────────────────────┘
└─────────────────────────────────────────────┘
↑
container_of计算出的结构体起始地址// offsetof宏计算成员在结构体中的偏移
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)工作原理:
(TYPE *)0:将0地址强制转换为TYPE类型指针&((TYPE *)0)->MEMBER:获取成员在"假想"结构体中的地址示例计算:
struct example {
int a; // 偏移量:0
char b; // 偏移量:4(假设int为4字节)
double c; // 偏移量:8(考虑内存对齐)
};
// offsetof(struct example, c) = 8计算公式:
结构体地址 = 成员地址 - 成员偏移量
↓
container = ptr - offsetof(type, member)// 实际数据结构
struct person {
int age;
char name[20];
struct list_head node; // 链表节点
};
// 假设我们知道node的地址
struct list_head *node_ptr = &some_person.node;
// 使用container_of
struct person *person_ptr = container_of(node_ptr, struct person, node);
// 展开计算过程:
// 1. offsetof(struct person, node) =
// (age:4字节 + name:20字节) = 24字节(假设对齐)
// 2. 结构体地址 = node_ptr - 24内存地址可视化:
地址 内容 说明
0x1000 [age] person结构体开始
0x1004 [name[0..19]] name字段
0x1018 [node.next] ← node成员开始
0x1020 [node.prev] 我们已知这里的地址:ptr=0x1018
计算过程:
ptr = 0x1018 (node的地址)
offset = 0x18 (24字节,node在person中的偏移)
person地址 = 0x1018 - 0x18 = 0x1000 ✓#include <stdio.h>
#include <stddef.h>
// 简化版offsetof和container_of
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
// 链表节点
struct list_head {
struct list_head *next;
};
// 业务数据
struct task {
int pid;
int priority;
struct list_head list; // 嵌入的链表节点
};
int main() {
// 创建一些任务
struct task task1 = {.pid = 100, .priority = 1};
struct task task2 = {.pid = 101, .priority = 2};
// 链接它们(简化版)
task1.list.next = &task2.list;
// 遍历链表:从list_head获取完整的task结构
struct list_head *current = &task1.list;
while (current != NULL) {
// 关键步骤:通过list_head找到包含它的task
struct task *task_ptr = container_of(current, struct task, list);
printf("Found task: pid=%d, priority=%d\n",
task_ptr->pid, task_ptr->priority);
current = current->next;
}
return 0;
}// Linux内核中的真实示例(简化)
struct inode {
// ... 各种字段 ...
struct hlist_node i_hash; // 哈希链表节点
};
// 在哈希表中查找inode
struct hlist_node *node = ...; // 从哈希表获取的节点
// 通过节点找到inode
struct inode *inode = container_of(node, struct inode, i_hash);// 内核中的实际实现包含类型检查
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void)); \
((type *)(__mptr - offsetof(type, member))); })内存对齐示例:
struct aligned_example {
char a; // 地址:0
// 填充3字节(假设int需要4字节对齐)
int b; // 地址:4
short c; // 地址:8
};
offsetof(struct aligned_example, c) = 8
而不是 1 + 4 = 5优势 | 说明 |
|---|---|
类型安全 | 编译时检查类型匹配 |
高效 | 编译时计算偏移,运行时只有减法操作 |
通用 | 适用于任何结构体和成员类型 |
内存友好 | 不需要为链表单独分配节点内存 |
A: char*指针的加减法以字节为单位,而其他类型指针的加减法以类型大小为步长。
A: 如果正确使用是安全的,但需要确保:
ptr确实指向type结构体中的membermember确实是type的成员A: 可以,但需要自己实现或使用<stddef.h>中的offsetof。
struct inner {
int data;
};
struct outer {
char tag;
struct inner embed;
float value;
};
struct inner *inner_ptr = &some_outer.embed;
struct outer *outer_ptr = container_of(inner_ptr, struct outer, embed);struct complex_struct {
int id;
struct list_head hash_node; // 用于哈希表
struct list_head lru_node; // 用于LRU缓存
struct list_head free_node; // 用于空闲列表
};
// 同一个结构体可以同时存在于多个链表中container_of宏是Linux内核中嵌入式数据结构模式的核心工具,它体现了C语言指针运算的强大能力。通过巧妙的偏移量计算,实现了从部分到整体的逆向查找,是内核高效链表实现的基础。
核心要点记忆:
offsetof(type, member)这种设计模式不仅在Linux内核中广泛应用,在许多嵌入式系统、高性能服务器和底层库中都有重要应用。