zoukankan      html  css  js  c++  java
  • Linux 内核中 offset_of 和 container_of 宏的实现

    Linux 内核中 offset_of 和 container_of 宏的实现

    offset_of

    via: https://elixir.bootlin.com/linux/latest/source/tools/include/linux/kernel.h#L23

    获取成员在结构体中的偏移量

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

    在以前我很奇怪,为什么用 0 指针
    后来在某个群里面和群主讨论了一下,发现这个设计很巧妙

    其实就是把地址 0当成是结构体的起始地址,这样,获取它的成员的地址,就相当于获取成员相在结构体里的偏移

    举个例子,有这样一个结构体

    struct demo {
      int i_var;
      int i2_var;
      int i3_var;
    };
    

    ((struct demo *) 0) 它是这样的

            +--------------+
    0x0     |              |
    0x1     |    i_var     |
    0x2     |              |
    0x3     |              |
            +--------------+
    0x4     |              |
    0x5     |    i2_var    |
    0x6     |              |
    0x7     |              |
            +--------------+
    0x8     |              |
    0x9     |    i3_var    |
    0xa     |              |
    0xb     |              |
            +--------------+
    

    我想获取 i3_var 的偏移量,只要 &(((struct demo *) 0) -> i3_var)
    得到的是 0x8

    可以看到这里是取 i3_var 地址,为什么是取地址?
    因为这个结构体的起始地址是 0i3_var 的地址肯定是:结构体的起始地址加上偏移量,因为起始地址是 0 所以取 i3_var 的地址就相当于是获得它在结构体里面的偏移量

    container_of

    via: https://elixir.bootlin.com/linux/latest/source/tools/include/linux/kernel.h#L26

    通过成员获取结构体地址

    参数

    @ptr:指向成员的指针

    @type:成员位于的结构体

    @member:成员在结构体里面的名称

    /**
     * container_of - cast a member of a structure out to the containing structure
     * @ptr:	the pointer to the member.
     * @type:	the type of the container struct this is embedded in.
     * @member:	the name of the member within the struct.
     *
     */
    #define container_of(ptr, type, member) ({			
    	const typeof(((type *)0)->member) * __mptr = (ptr);	
    	(type *)((char *)__mptr - offsetof(type, member)); })
    

    开始分析之前需要补一下 {()} 表达式

    这个表达式就是返回最后一条语句的 运算结果 记住必须是 运算表达式 或者 是一个 ,不能是赋值语句(不知道该怎么表达,自己多写几个 demo 试试就知道了)

    demo

    #include <stdio.h>
    
    int main() {
        int v = ({
                int v2 = 1;
                int v3 = 2;
                v2 + v3;
        });
        printf("%d
    ", v);
        return 0;
    }
    

    上面的程序运行结果会是 3

    #include <stdio.h>
    
    int main() {
        int v = ({
                int v2 = 1;
                int v3 = 2;
                0;
        });
        printf("%d
    ", v);
        return 0;
    }
    

    上面的程序运行结果会是 0

    错误演示

    #include <stdio.h>
    
    int main() {
        int v = ({
                int v2 = 1;
                int v3 = 2;
                int v4 = v2 + v3;
        });
        printf("%d
    ", v);
        return 0;
    }
    

    gcc 编译的话会直接报错

    main.c: 在函数‘main’中:
    main.c:4:13: 错误:void 值未如预期地被忽略
        4 |     int v = ({
          |             ^
    

    回归正题一句一句分析

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

    获取 member 的类型,然后声明一个 对应 member 类型的常量指针(在常量指针中,指针指向的内容是不可改变的,指针看起来好像指向了一个常量) __mptr__mptr = ptr,这样保证我们的操作不会误操作修改了 ptr导致程序出错,反正就是 __mptr 是指向已知成员的地址的,并且是不可修改的

    typeofcGNU 扩展函数,它会返回参数对应的类型

    int int_var = 0;

    typeof(int_var) 会得到 int 类型

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

    先把 __mptr 转成 char * 然后在减去 member 在结构体里的偏移量就能得到结构体的真实地址

    为什么要先把 __mptr 转成 char *

    因为对指针进行加减运算的时候并不是说 addr + 1 得到的地址就是 addr 加上 1,实际上加的是 1 * (sizeof(typeof(*addr))) (自己细品),转成 char * 后就相当于 sizeof(typeof(char)),其实就是 1,加 n 变成:(n * 1)

    现在一个结构体 demo

    struct demo {
        int i_var;
        int i2_var;
        int i3_var;
    };
    
    offset                         address
    
               +--------------+
       0x0     |              |	0x7ffd7a0a8c0c
       0x1     |    i_var     |
       0x2     |              |
       0x3     |              |
               +--------------+
       0x4     |              |	0x7ffd7a0a810
       0x5     |    i2_var    |
       0x6     |              |
       0x7     |              |
               +--------------+
       0x8     |              |	0x7ffd7a0a814
       0x9     |    i3_var    |
       0xa     |              |
       0xb     |              |
               +--------------+
    

    我要通过 i3_var 的地址去找对应的 demo 结构体实例的地址

    假设 i3_var 的地址是 0x7ffd7a0a8d4 偏移量是 0x8,只要用地址减去偏移量就能获得其对应的 demo 结构体实例的地址

    其实这两个宏相关的文章有很多人写过,我看过很多,写的都比我的好,我只是想以自己的理解去写一篇

    可以看看这一篇:https://radek.io/2012/11/10/magical-container_of-macro/

  • 相关阅读:
    MYSQL 之 JDBC(十三):处理事务
    MYSQL 之 JDBC(十四):批量处理JDBC语句提高处理效率
    MYSQL 之 JDBC(十五):数据库连接池
    MYSQL 之 JDBC(十六): DBUtils
    MYSQL 之 JDBC(十七): 调用函数&存储过程
    Android初级教程理论知识(第八章网络编程二)
    android6.0SDK 删除HttpClient的相关类的解决方法
    iOS下WebRTC音视频通话(三)-音视频通话
    love~LBJ,奥布莱恩神杯3
    Android简易实战教程--第二话《两种进度条》
  • 原文地址:https://www.cnblogs.com/crybaby/p/13274408.html
Copyright © 2011-2022 走看看