zoukankan      html  css  js  c++  java
  • CONTAINING_RECORD宏

    宏 CONTAINING_RECORD 的用处其实还是相当大的, 而且很是方便, 它的主要作用是:根据结构体中的某成员的地址来推算出该结构体整体的地址

    下面从一个简单的例子开始说起, 我们定义一个结构体, 同时类型化:

    typedef struct
    {
        int a;
        int b;
        int c;
    }ss;

    这是一个很简单的结构体, 没什么特殊的, 稍微分析下该结构体(假设在32位平台上):

    1. 结构体的大小(字节):4+4+4=12字节
    2. 成员a的偏移:0
    3. 成员b的偏移:4
    4. 成员c的偏移:8

    我们用ss来定义一个变量: ss s = {1,2,3};

    那么此时a,b,c的值分别为: a=1,b=2,c=3.

    其实编译器在生成代码的时候其实是这样给成员变量赋值的:

    假定s的地址为:0x12000000, 则:

    *(int*)((char*)&s + 0) = 1;
    *(int*)((char*)&s + 4) = 2;
    *(int*)((char*)&s + 8) = 3;

    也就是说是在&s的地址基础上加上变量的偏移来确定成员的指针并赋值的, 所以:

    &s->a 将得到 0x12000000 + 0 = 0x12000000
    &s->b 将得到 0x12000000 + 4 = 0x12000004
    &s->c 将得到 0x12000000 + 8 = 0x12000008

    所以:    结构体的地址 + 成员变量的偏移 = 成员变量的地址
    移一下项:  成员变量的地址 - 成员变量的偏移 = 结构体的地址
    哇哇, 这就是我们想要的地址, 不就是做了个减法嘛~~~囧

    首先, 成员变量的地址是我们知道的. 其次, 我们需要得到成员变量的偏移(假定为成员b的偏移).

    怎么办呢? 我们可以这样:

    &s->b - (unsigned long)&s, 这样就可以得到成员b的偏移了, 但是, 但是, &s 是我们需要的, 显然暂时是个未知数, 既然这样...

    那, 我们再做一次减法吧(非正确的C语言表达式, 不过结果没问题, 这里只是显得清楚点):

    &(s-s)->b - (unsinged long)&(s-s)

    其中, 为保证类型一致, 需要:

    s-s = (ss*)0

    (unsigned long)&(s-s) = (unsigned long)(ss*)0 = 0, 直接省略该部分就可以了

    那么, 化简得到: &((ss*)0)->b - (unsigned long)0

    最简结果: &((ss*)0)->b, 这就是b的偏移

    哈哈, 很简单吧, 0指针的妙用, 总共做了两次减法而已~ 对你们数学帝来说肯定不是问题啦~

    其中, 我们需要知道ss结构体的原型, ss结构体中的某个成员变量b(其实无论哪个都一样, 只是要和前面提供指针的那个变量要一致)

     

    总结下, 我们需要提供:结构体中某个成员变量的地址, 该结构体的原型, 该结构体中的某个成员变量(与前面要是同一个变量)

    最终的CONTAINING_RECORD的定义为:

    #define CONTAINING_RECORD(addr,type,field) ((type*)((unsigned char*)addr - (unsigned long)&((type*)0)->field))
        // addr:  结构体中某个成员变量的地址
        // type:  结构体的原型
        // field: 结构体的某个成员(与前面相同)
    

    好了, 所有的结论都出来了, 这是一个万能公式, 不管成员变量是哪一个结果都正确, 这是相对于知道第一个变量的地址而言的:

    如果知道的是第一个成员的地址(pa = &s->a)的话, 这是最简单的情况了:

    直接强制类型转换就可以了: (ss*)pa 即可, 此时 &((type*)0)->field 这部分恰好为0

    所以结果直接就是((type*)addr)了, 最简单的情况. 也是我们最容易想到的一种情况, 比如把链表元素放在结构体的最开始 ~~~

     

    到这里这个CONTAINING_RECORD宏就已经说完了~

    现在, 我们在使用LIST_ENTRY等双向链表时, 不管把该链表放在结构体的哪个地方, 都可以在遍历链表时通过CONTAINING_RECORD宏来准确得到整个结构体的地址了~ 记得移除链表中的某个元素的时候, 要free整个结构体的地址才行哦, WDK提供的操作函数只是把该链表元素脱离整个链表~~~

     

    btw:

      1. 把addr转换为 unsigned char* 的原因是在指针计算时的计算单位为1, 也就是说 (unsigned char*)addr+1 = addr+1, 不转换的话肯定是错误的
      2. &((type*)0)->field转换为 (unsigned long) 4个字节宽的同时是要保证表达式不是由两个指针的算术操作构成的, 因为C语言标准未定义那样的运算

    转载:

    https://blog.twofei.com/546/

  • 相关阅读:
    idea设置全局ignore
    win 2012 安装mysql 5.7.20 及报错 This application requires Visual Studio 2013 Redistributable. Please ins
    win 2012 安装mysql 5.7.20 及报错 This application requires Visual Studio 2013 Redistr
    kafka 删除 topic
    java编译中出现了Exception in thread “main" java.lang.UnsupportedClassVersionError
    Centos中使用yum安装java时,没有jps的问题的解决
    Spring 整合Junit
    Spring纯注解配置
    Spring 基于注解的 IOC 配置
    打印java系统的信息
  • 原文地址:https://www.cnblogs.com/DeeLMind/p/6927299.html
Copyright © 2011-2022 走看看