• Linux内核宏Container_Of


    1. 结构体在内存中是如何存储的

    typedef struct student 
    { 
     int id; 
     char name[30]; 
     int math; 
    } Student; 
    
    int main() 
    { 
     
     Student stu; 
     stu.id = 123456; 
     strcpy(stu.name,"feizhufeifei"); 
     stu.math = 90; 
     stu.PE = 80; 
     printf("Student:%p\r\n",&stu); 
     printf("stu.ID:%p\r\n",&stu.ID); 
     printf("stu.name:%p\r\n",&stu.name); 
     printf("stu.math:%p\r\n",&stu.math); 
     return 0; 
    } 
    
    /**  打印结果如下: */
    
    //结构体的地址 
    // Student:0xffffcbb0 
    //结构体第一个成员的地址 
    // stu.ID:0xffffcbb0  //偏移地址 +0 
    // stu.name:0xffffcbb4//偏移地址 +4 
    // stu.math:0xffffcbd4//偏移地址 +24 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    我们可以看到,结构体的地址和结构体第一个成员的地址是相同的( x )。
    不太理解的再看下这两个例子:

    • struct A { int a; char b; int c; char d; };a 偏移为 0 , b 偏移为 4 , c 偏移为 8 (大于 4 + 1 的 4 的最小整数倍), d 偏移为 12 。A 对齐为 4 ,大小为 16 。
    • struct B { int a; char b; char c; long d; };a 偏移为 0 , b 偏移为 4 , c 偏移为 5 , d 偏移为 8 。B 对齐为 8 , 大小为 16 。

    以上情况只针对 POD 类型的结构体,具体见《深入理解c++11: c++11的特性与使用》POD类型章节 , 对于非POD类型是不成立的,但是这并不影响利用成员变量的偏移地址计算结构体首地址。例如:

    #include 
    #include 
    #include 
    
    #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) );}) 
    
    
    typedef struct chinese {
     private:
        int a;
        char b;
    } Chinese;
    
    typedef struct studenta : Chinese
    { 
     int id; 
     char name[30]; 
     int math; 
    } ChinaStudent; 
    
    typedef struct studentb
    { 
     int id; 
     char name[30]; 
     int math; 
    }Student; 
     
    int main() 
    { 
        ChinaStudent a;
        Student b;
        printf("a.id: %d, a: %d\n", &a.id, &a);
        printf("b.id: %d, b: %d\n", &b.id, &b);
        return 0;  
    } 
    
    /** 打印结果 */
    // a.id: 1827697936, a: 1827697928
    // b.id: 1827697888, b: 1827697888
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    我们可以看到,结构体中成员变量在内存中存储的其实是偏移地址。也就是说 结构体A的地址+成员变量的偏移地址 = 结构体成员变量的起始地址

    因此,我们也可以根据 结构体变量的起始地址和成员变量的偏移地址来反推出结构体A的地址

    2. container_of

    #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)); }) 
    
    • 1
    • 2
    • 3
    • 4

    首先看下三个参数:

    • ptr是成员变量的指针
    • type是指结构体的类型
    • member是成员变量的名字。

    container_of宏的作用是 通过结构体内某个成员变量的地址和该变量名,以及结构体类型, 找到该结构体变量的地址。这里使用的是一个利用编译器技术的小技巧,即 先求得结构成员在结构中的偏移量,然后根据成员变量的地址反过来得出主结构变量的地址。下面具体分析下各个部分。

    2.1 typeof

    首先看下typeof,是用于返回一个变量的类型,这是GCC编译器的一个扩展功能,也就是说typeof是编译器相关的。既不是C语言规范的所要求,也不是某个标准的一部分。

    int main() 
    { 
     int a = 5; 
     //这里定义一个和a类型相同的变量b 
     typeof(a) b  = 6; 
     printf("%d,%d\r\n",a,b);//5 6 
     return 0; 
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.2 (((type *)0)->member)

    ((TYPE *)0) 将0转换为 type 类型的结构体指针,换句话说就是让编译器认为这个结构体是开始于程序段起始位置0,开始于0地址的话,我们得到的成员变量的地址就直接等于成员变量的偏移地址了。

    (((type *)0)->member) 引用结构体中MEMBER成员。

    2.3 const typeof(((type * )0) ->member)*__mptr = (ptr);

    这句代码意思是用 typeof() 获取结构体里member成员属性的类型,然后定义一个该类型的临时指针变量 __mptr,并将ptr所指向的member的地址赋给 __mptr;

    为什么不直接使用 ptr 而要多此一举呢?我想可能是为了避免对 ptrptr 指向的内容造成破坏

    2.4 offsetof(type, member))

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

    size_t 是标准C库中定义的,在32位架构中被普遍定义为:

    typedef unsigned int size_t;
    
    • 1

    而在64位架构中被定义为:

    typedef unsigned long size_t;
    
    • 1

    官方解释是:取某个结构体成员与结构体本身起始地址的偏移大小

    • (TYPE* 0),先把 0 地址强转成type类型,也就是传入的结构体类型
    • & 取地址符,取的是TYPE类型结构体的MEMBER 变量的地址
    • 其次再仔细想想,取 0 地址的MEMBER 变量的地址,那么就直接得到该 MEMBER变量与0地址的偏移大小

    也就与官方解释对应上了

    2.5 (type * )((char * )__mptr - offsetof(type, member))

    这句话的意思就是,把 __mptr 转换成 char* 类型。因为 offsetof 得到的偏移量是以字节为单位。两者相减得到结构体的起始位置, 再强制转换成 type 类型

    3. 举例

    #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) );}) 
             
    typedef struct student 
    { 
     int id; 
     char name[30]; 
     int math; 
    }Student; 
     
    int main() 
    { 
        Student stu; 
            Student *sptr = NULL; 
      stu.id = 123456; 
      strcpy(stu.name,"zhongyi"); 
      stu.math = 90; 
            sptr = container_of(&stu.id,Student,id); 
            printf("sptr=%p\n",sptr); 
            sptr = container_of(&stu.name,Student,name); 
            printf("sptr=%p\n",sptr); 
            sptr = container_of(&stu.math,Student,id); 
            printf("sptr=%p\n",sptr); 
            return 0;  
    } 
    
    /** 运行结果/
    // sptr=0x16ddf7510
    // sptr=0x16ddf7510
    // sptr=0x16ddf7510
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    参考文章1
    参考文章2

  • 相关阅读:
    拼多多店铺营业执照相关问题
    Socks5代理与IP代理:网络安全与爬虫中的应用
    【Hack The Box】linux练习-- Previse
    # maven-高级
    大模型的实践应用4-ChatGLM-6b大模型的结构与核心代码解读,最全的ChatGLM模型架构介绍与源码解读
    openGauss学习笔记-78 openGauss 数据库管理-内存优化表MOT管理-内存表特性-MOT关键技术
    Java SE 学习笔记(十四)—— IO流(3)
    卓越进行时 | 聚焦网络安全学科建设 深化校企合作协同育人
    下拉菜单选择播放视频后如何更改video标签
    羊城杯2022 部分web
  • 原文地址:https://blog.csdn.net/qq_37635373/article/details/126807719