5. container_of 宏的定义、作用及手动实现详细解释
1. 宏的定义:
#define offsetof(type, member) ((size_t)&(((type *)0)->member))
#define container_of(ptr, type, member) \((type *)((char *)(ptr) - offsetof(type, member)))
2. 宏的作用:
container_of 宏用于通过一个结构体成员的指针,推算出整个结构体的首地址。在内核开发或系统编程中,通常通过链表节点、树节点等数据结构的成员来操作,而 container_of 宏可以通过这些成员的指针轻松获取包含该成员的整个结构体。
3. 宏的实现原理:
ptr: 指向结构体成员的指针。type: 结构体的类型。member: 结构体中的某个成员名。
实现的核心原理是利用指针运算和结构体成员的偏移量来获得结构体的起始地址。偏移量是通过标准的 offsetof 宏计算出来的,它返回的是结构体成员相对于结构体开头的字节数。
工作原理:
offsetof(type, member)返回member相对于type结构体起始地址的偏移量(原理在手动实现中详细解释)。(char *)(ptr)将成员指针ptr转换为字节指针,以便按字节进行减法运算。((char *)(ptr) - offsetof(type, member))从成员的地址ptr中减去成员相对于结构体起始位置的偏移量,从而计算出整个结构体的首地址。- 最后,将结果转换为
type*类型的指针,表示整个结构体的首地址。
4. 手动实现 container_of 宏并解释
为了手动实现 container_of 宏,我们需要依赖于结构体成员的地址以及其在结构体中的偏移量。我们可以不使用标准的 offsetof,而是手动计算成员的偏移量。以下是 container_of 宏的手动实现:
手动实现:
#define container_of(ptr, type, member) \((type *)((char *)(ptr) - ((size_t)&(((type *)0)->member))))
实现过程的详细解释:
4.1 ((type *)0):
这是将整数 0 转换为 type 类型的指针,也就是说,假设一个结构体的首地址为 0。我们实际上没有在内存中分配任何空间,这仅仅是一个假设操作。该操作的作用是用于后续计算结构体成员的偏移量。
(type *)0
4.2 (((type *)0)->member):
通过 ((type *)0) 得到一个指向结构体类型为 type 且地址为 0 的假设结构体。接着通过 ->member 访问该假设结构体的 member 成员。因为结构体假设位于内存地址 0,此时 member 的地址就是其相对于结构体开头的偏移量。
&(((type *)0)->member)
这一步将返回 member 成员相对于结构体首地址的偏移量,并且返回的是指针形式。我们通过 & 符号取 member 成员的地址。
4.3 (size_t)&(((type *)0)->member):
这一行将上一步获得的 member 成员的地址(实际上是相对偏移量)转换为 size_t 类型。size_t 是无符号整数类型,可以存储偏移量。
4.4 (char *)(ptr):
将传入的 ptr(指向 member 成员的指针)转换为 char* 类型指针。这样做的原因是 char* 类型指针按字节处理,这使得我们可以以字节为单位进行地址操作。
4.5 ((char *)(ptr) - ((size_t)&(((type *)0)->member))):
这一步是核心操作。通过将成员指针 ptr 减去成员 member 在结构体中的偏移量,来计算结构体的首地址。这样,我们就可以从成员指针 ptr 中推算出包含该成员的结构体的起始地址。
4.6 (type *)(...):
最后,将计算出的结构体首地址转换为 type* 类型的指针,即指向整个结构体的指针。
示例代码:
#include <stdio.h>struct MyStruct {int a;float b;char c;
};#define container_of(ptr, type, member) \((type *)((char *)(ptr) - ((size_t)&(((type *)0)->member))))int main() {struct MyStruct s = {1, 2.5, 'A'};// 获取成员 c 的指针char *ptr_to_c = &s.c;// 使用 container_of 获取结构体的首地址struct MyStruct *struct_ptr = container_of(ptr_to_c, struct MyStruct, c);// 验证计算出的地址是否正确printf("Original struct address: %p \n", (void*)&s);printf("Calculated struct address: %p \n", (void*)struct_ptr);return 0;
}
运行结果:
Original struct address: 0x7ffee58fa8f0
Calculated struct address: 0x7ffee58fa8f0
总结:
container_of宏通过成员指针ptr计算出结构体的起始地址。- 计算的关键步骤是利用成员的偏移量,通过指针减法推算结构体的首地址。
- 手动实现中,通过创建一个虚拟的结构体(位于地址
0)来获取成员的偏移量。
