• 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/

  • 相关阅读:
    在JS和.NET中使用JSON (以及使用Linq to JSON定制JSON数据)
    转载JSON格式化工具
    bzoj3771 Triple
    hdu 2082 找单词
    bzoj 3143: [Hnoi2013]游走
    Wannafly挑战赛17 B
    基尔霍夫矩阵
    矩阵&行列式
    luogu P2421 [NOI2002]荒岛野人
    bzoj 2818: Gcd
  • 原文地址:https://www.cnblogs.com/DeeLMind/p/6927299.html
Copyright © 2020-2023  润新知