container_of,typeof详解

本文详细介绍了C语言中用于通过成员指针获取容器结构体指针的container_of宏的运行机制,包括前置宏offsetof的用途、中间变量__mptr的定义及其作用,以及整个宏如何巧妙地通过成员找到容器。同时,通过实例代码展示了如何在实际编程中应用这一宏。
  1. 1.源码
  2. /**
  3.  * container_of - 通过结构体的一个成员获取容器结构体的指针
  4.  * @ptr: 指向成员的指针。
  5.  * @type: 成员所嵌入的容器结构体类型。
  6.  * @member: 结构体中的成员名。
  7.  *
  8.  */
  9. #define container_of(ptr, type, member) ({ \
  10.     const typeof( ((type *)0)->member ) *__mptr = (ptr); \
  11.    (type *)( (char *)__mptr - offsetof(type,member) );})
  12.  这个宏的作用,就是通过一个容器(结构体)中某个成员的指针得到指向这个容器(结构体)的指针,简单的说就是通过成员  找容器。
  13. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)


  14. 2.用法
  15. 例如:

    typedef struct frame {
            int a;
            char b;
            char name[10];
    } frame_t;


    int main(int argc, char **argv)
    {
            frame_t fra, *pf;
            fra.a = 1;
            fra.b = 2;
            snprintf(fra.name, 5, "cjz%d", 1);


            pf = container_of(&fra.a, frame_t, a);
            printf("fra.a = %d, fra.b = %d, fra.name = %s\n", fra.a, fra.b, fra.name);


            return 0;
    }

    参考:http://blog.youkuaiyun.com/cuijianzhongswust/article/details/8249352
  16. 3.详解

  17. 先分析一下这个 宏的运行机理:

  18. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
    一共4步 
    1. ( (TYPE *)0 ) 将零转型为TYPE类型指针; 
    2. ((TYPE *)0)->MEMBER 访问结构中的数据成员; 
    3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址; 
    4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型。巧妙之处在于将0转 换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址;

  19. 注释:

    1.要将地址赋给指针, int *p = 0x12345678是不对的 。正确的方式应为:int *p = (int *) 0x12345678。在大多数计算机中,内存地址确实是以无符号整型数来表示的,而且多以16进制表示,但我们在C语言中不能用整型数去表示地址,只能用指针常量来表示,因为它是被用来赋给一个指针的。

    对于这个赋值问题还可以换一个角度去理解,在C语言中,使用赋值操作符时,赋值操作符左边和右边的表达式类型应该是相同的,如果不是,赋值操作符将试图把右边表达式的值转换为左边的类型。所以如果写出int *p = 0x12345678 ; 这条语句编译器会报错:'=' : cannot convert from ' const int ' to ' int * ' ,因为赋值操作符左边和右边的表达式的类型应该相同,而0x12345678是int型常量,p是一个指向int型的指针,两者类型不同,所以正确的方式是:int *p = (int *) 0x12345678 ; 

  20. 故((TYPE*)0)是将地址为0转换成TYPE*型指针。即地址0处存放指向TYPE类型数据的地址。则((TYPE*)0)->MEMBER指向TYPE类型数据的成员MEMBER.((TYPE*)0)->MEMBER相当于(*((TYPE*)0)).MEMBER.

  21.  

  22. 参考:http://blog.chinaunix.net/uid-28458801-id-4200573.html

  23. (1)const typeof( ((type *)0)->member ) *__mptr = (ptr);                                          typeof即将参数*_mptr定义为((type *)0)->member类型,即复制ptr。
    定义一个中间变量__mptr,它等于提供给宏的参数ptr,也就是指向某个成员的指针。这个中间变量的命名意义是:
    "__"代表内部使用,内核编程中常常这么做;
    “m”代表middle。
    1.     为了避免对 ptr及prt指向的内容造成破坏,这里不直接使用 ptr 而要多多加一个__mptr。
    (2)(type *)( (char *)__mptr - offsetof(type,member) );
    这行代码的作用是通过中间变量__mptr(指向某个成员的指针)减去这个成员在容器(结构体)中的偏移来得到指向容   器(结构体)的指针。 把__mptr转换成 char *类型,因为offsetof得到的偏移量是以字节为单位。两者相减得到结构体的起始位置,再强制转      换成type类型。

    参考:http://blog.chinaunix.net/uid-20543672-id-3205315.html



<think>我们被要求解释container_of宏的用法和原理。根据提供的引用资料,我们知道container_of宏在Linux内核中非常重要,它用于通过结构体中的某个成员变量的地址推导出整个结构体的起始地址。这在处理链表等数据结构时尤其有用。首先,我们回顾一下结构体在内存中的存储。结构体中的成员在内存中是连续存储的(尽管可能有对齐填充)。因此,如果我们知道结构体中某个成员的地址,我们可以通过减去该成员在结构体中的偏移量来得到结构体的起始地址。container_of宏的定义通常如下(取自Linux内核): ```c#define container_of(ptr,type,member)({ \consttypeof(((type*)0)->member) *__mptr =(ptr); \(type *)( (char *)__mptr -offsetof(type,member) );}) ```或者,另一种常见的写法(使用offsetof): ```c#define container_of(ptr,type,member)\(type *)( (char *)(ptr) -offsetof(type,member)) ```这里我们解释一下:1.`ptr`:指向结构体成员的指针。 2. `type`:结构体的类型。 3. `member`:结构体中该成员的名字。 宏的功能:给定一个指向结构体成员的指针`ptr`,它所属的结构体类型`type`,以及成员名`member`,返回指向整个结构体的指针。原理:-首先,我们需要计算成员`member`在结构体`type`中的偏移量。这可以通过`offsetof(type, member)`获得。`offsetof`是一个标准宏(在stddef.h中定义),用于计算结构体成员相对于结构体起始地址的偏移量。-然后,我们将给定的成员指针`ptr`(它是一个地址)减去这个偏移量,就得到了结构体的起始地址。 -注意:因为指针运算时,加减的值会根据指针类型进行调整(比如,如果指针是int*,那么加1就前进4个字节),所以我们需要先将`ptr`转换为`char*`,因为`char*`的加减操作是以字节为单位的。 另外,在第一个定义中,有一行:```cconsttypeof(((type*)0)->member) *__mptr =(ptr); ```这行代码有两个作用:1.检查`ptr`的类型是否与`type`结构体中`member`的类型一致(通过声明一个同类型的指针并赋值)。2.避免`ptr`是一个表达式(比如函数调用)时被多次求值(因为它被赋值给了一个变量__mptr)。但是,在简化版本中,我们通常只使用第二行,因为`offsetof`已经可以完成工作。 现在,我们举一个例子来说明: 假设我们有一个结构体:```cstructmy_struct {inta;char b;double c; };``` 我们有一个指向成员`c`的指针:`double*ptr= &my_struct_instance.c;`,我们想得到整个结构体的地址。 首先,计算`c`在结构体中的偏移量:`offsetof(structmy_struct, c)`。假设在某个系统上,`int`占4字节,`char`占1字节,然后有3字节填充(为了对齐`double`),那么`c`的偏移量就是8(4+1+3=8)。然后,将`ptr`(即`&c`)减去8个字节,就得到了结构体的起始地址。 使用container_of宏: ```cstruct my_struct *p =container_of(ptr, structmy_struct, c); ```这样,`p`就指向了包含这个`c`的结构体实例。 应用场景:在Linux内核中,链表(list_head)的实现就大量使用了container_of宏。链表节点通常被嵌入到其他结构体中,当我们遍历链表时,每个节点是一个list_head结构,但我们需要访问包含该节点的整个结构体,这时就用container_of宏。例如:```cstructlist_head{struct list_head *next,*prev; };struct my_struct {intdata;structlist_headlist;};//假设我们有一个指向list_head的指针posstructlist_head *pos;// ...在遍历链表时,pos指向当前节点的list_head//通过pos获取包含它的my_structstruct my_struct *item =container_of(pos,struct my_struct,list);``` 这样,我们就可以通过链表节点访问到我们自定义结构体中的数据(如`data`)。 注意事项: -使用container_of宏时,必须确保传入的指针确实是该结构体成员的正确指针。 -在用户空间也可以使用container_of,但需要自己定义offsetof(如果不在标准头文件中)和container_of宏。总结:container_of宏的核心原理就是通过成员指针减去该成员在结构体中的偏移量得到结构体首地址。它利用了结构体在内存中连续存储的特性。 相关问题: 1.为什么在container_of宏中需要将指针转换为char*类型?2.如何自己实现offsetof宏? 3.在Linux内核中,除了链表,还有哪些地方使用了container_of宏?</think>### container_of详解 #### 核心功能 `container_of` 宏用于**通过结构体成员的地址反推整个结构体的起始地址**。其函数签名为: ```c container_of(ptr, type, member) ``` - `ptr`:指向结构体成员的指针 - `type`:目标结构体类型 - `member`:成员在结构体中的名称 #### 实现原理 标准实现(简化版): ```c #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type, member) );}) ``` **关键步骤解析**: 1. **类型安全校验**(第一行): ```c const typeof( ((type *)0)->member ) *__mptr = (ptr); ``` - `typeof( ((type *)0)->member )` 获取成员类型 - 声明临时指针 `__mptr` 并赋值,验证 `ptr` 的类型正确性 2. **地址计算**(第二行): ```c (type *)( (char *)__mptr - offsetof(type, member) ); ``` - `offsetof(type, member)` 计算成员在结构体中的偏移量 - 将成员指针转为 `char*`(确保字节级计算) - 减去偏移量得到结构体起始地址 > **关键机制**:结构体在内存中连续存储,成员地址 = 结构体首地址 + 成员偏移量[^1]。 #### 典型应用场景 1. **Linux 内核链表操作**: ```c struct list_head { struct list_head *next, *prev; }; struct task { int pid; struct list_head node; // 嵌入链表节点 }; // 通过链表节点获取宿主结构体 struct task *get_task(struct list_head *ptr) { return container_of(ptr, struct task, node); } ``` 2. **设备驱动开发**: ```c struct usb_device { int vendor_id; struct usb_endpoint ep; // 端点成员 }; void handle_endpoint(struct usb_endpoint *ep_ptr) { struct usb_device *dev = container_of(ep_ptr, struct usb_device, ep); // 通过端点操作整个设备 } ``` #### 使用注意事项 1. **指针有效性**:`ptr` 必须指向有效的结构体成员 2. **类型匹配**:`type` 和 `member` 需与实际定义严格一致 3. **内存对齐**:结构体需满足内存对齐规则,否则偏移量计算错误 4. **跨平台移植**:用户空间使用时需自行实现 `offsetof`(标准库通常提供) > **为什么用 `char*` 转换**? > 避免指针算术运算的类型自动缩放(`char*` 以字节为单位计算偏移量)[^2]。 #### 实战示例 ```c #include <stddef.h> // 定义 offsetof // 自定义结构体 struct student { int id; char name[20]; float gpa; // 目标成员 }; int main() { struct student s = {1, "Alice", 3.8}; float *pgpa = &s.gpa; // 成员指针 // 通过 gpa 成员反推 student 结构体地址 struct student *ps = container_of(pgpa, struct student, gpa); // 验证结果 printf("Original: %p\nRecovered: %p\n", &s, ps); // 输出相同地址(如 0x7ffd42a1b6c0) return 0; } ``` ### 总结要点 | 特性 | 说明 | |--------------|----------------------------------------------------------------------| | **核心目的** | 通过成员地址获取宿主结构体地址 | | **底层依赖** | 结构体连续存储原理 + 成员偏移量计算 | | **关键操作** | `offsetof` 获取偏移量,`char*` 类型转换确保字节计算 | | **典型场景** | Linux 内核链表、设备驱动、嵌入式数据结构操作 | | **优势** | 避免冗余指针存储,实现零开销数据结构嵌入 |
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值