container_of分析

本文详细解释了Linux内核中container_of宏的工作原理。该宏能够根据结构体成员指针找到所属结构体指针,文中通过具体步骤展示了其实现机制,并介绍了offsetof宏的应用。

container_of是linux内核中常用的一个宏,这个宏的功能是,根据某个结构体字段的指针,找到对应的结构体指针。

/**
 * container_of - 通过结构体的一个成员获取容器结构体的指针
 * @ptr: 指向成员的指针。
 * @type: 成员所嵌入的容器结构体类型。
 * @member: 结构体中ptr所对应的成员名。
 *
 */
#define container_of(ptr, type, member) ({ \
    const typeof( ((type *)0)->member ) *__mptr = (ptr); \
    (type *)( (char *)__mptr - offsetof(type,member) );})

第一步,首先定义一个临时的数据类型(通过typeof( ((type *)0)->member )获得)与ptr相同的指针变量__mptr,然后用它来保存ptr的值。把ptr重新赋值给了__mptr。

    看似多余,实际上这里起到了给开发者提醒的功能。如果开发者传入的ptr指针指向的类型,与结构体中成员的类型不符,编译器在这里会打印一条warning,提示开发者可能存在的错误。

    定义一个中间变量__mptr,它等于提供给宏的参数ptr,也就是指向某个成员的指针。这个中间变量的命名意义是:
"__"代表内部使用,内核编程中常常这么做;
“m”代表middle。


第二步,用(char *)__mptr减去member在结构体中的偏移量,得到的值就是整个结构体变量的首地址(整个宏的返回值就是这个首地址)。

    注意偏移的获取offsetof宏的实现

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

    将地址0强制转换为type *,那么0指向某一个type类型的对象,也就是此type对象的地址,那么(TYPE *)0)->MEMBER就是type的member域,现在取址后&((TYPE *)0)->MEMBER 就是member域的地址,因为type的地址为0,则member的地址实质上就是它相对于type地址的偏移。

    这里为什么可以这样实现而不会出错呢?有两点原因,其一,地址0是在编译器编译时已经指定好了的,其二,这里全部都是取址操作,并没内存数据访问,因此不会存在非法访问内存的问题。


    typeof是GNU对C新增的一个扩展关键字,用于获取一个对象的类型,在很多时候我们处理的对象通常是一个指针,而此时如果想知道指针所指向的对象的类型,typeof就派上用场了,详见GNU的官方文档:http://gcc.gnu.org/onlinedocs/gcc/Typeof.html 




container_of宏的作用是从包含在某个结构体中的结构成员获取结构体本身的指针,也就是通过结构体变量中的某个成员的首地址获取结构体的首地址。其实现原理如下: ### 原型定义 ```c #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) * __mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); }) #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) ``` ### 原理分析 1. **offsetof宏的作用**:`offsetof(TYPE, MEMBER)` 宏用于计算结构体 `TYPE` 中成员 `MEMBER` 相对于结构体起始地址的偏移量。它的实现方式是将 `0` 强制转换为 `TYPE *` 类型的指针,这意味着将地址 `0` 视为结构体的起始地址。然后通过 `->` 操作符访问成员 `MEMBER`,并取其地址。由于结构体起始地址为 `0`,所以该成员的地址值就等于其相对于结构体起始位置的偏移量。最后将其转换为 `size_t` 类型返回 [^1][^4]。 2. **container_of宏的实现**: - 首先,`const typeof(((type *)0)->member) * __mptr = (ptr);` 这一行代码定义了一个临时指针 `__mptr`,其类型与结构体 `type` 中成员 `member` 的类型相同,并将传入的成员指针 `ptr` 赋值给它。这一步主要用于类型检查,确保传入的指针类型与结构体成员类型一致 [^1][^4]。 - 接着,`(type *)((char *)__mptr - offsetof(type, member));` 这一行是核心计算部分。先将 `__mptr` 强制转换为 `char *` 类型,这是因为 `char` 类型的指针在进行指针运算时,每次移动的步长为 `1` 字节。然后用成员指针 `__mptr` 减去成员相对于结构体起始位置的偏移量,得到结构体的起始地址。最后将结果强制转换为 `type *` 类型,即结构体指针类型 [^1][^4]。 ### 简单示例 ```c #include <stdio.h> #include <stddef.h> #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) * __mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); }) // 定义一个结构体 struct example { int a; char b; double c; }; int main() { struct example var; // 获取成员 c 的地址 double *c_ptr = &var.c; // 通过 container_of 宏获取结构体的地址 struct example *ptr = container_of(c_ptr, struct example, c); // 验证获取的结构体地址是否正确 if (ptr == &var) { printf("Successfully retrieved the address of the structure!\n"); } return 0; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值