Windows 经典的宏——CONTAINING_RECORD

在windows ddk中提供了一个经典的宏,其定义如下:
#define CONTAININT_RECORD(address, type, field) /
             ((type*)((PCHAR)(address) - (PCHAR)(&((type*)0)->field)))

这个宏用于取得内存中任何结构体的首地址,要提供的参数是:结构体中某个成员(field)的地址address、结构体的类型type、提供地址那个成员的名字field。


今天看到别人的代码用了 CONTAINING_RECORD 这样的一个宏,我看了它的定义,如下:
#define CONTAINING_RECORD(address, type, field) ((type *)( (PCHAR)(address) - (ULONG_PTR)(&((type*)0)->field)))

class A
{
      char c;
      int a;
      short b;
}

int a = 100;
int *pInt = &a;
比如,我调用了 CONTAINING_RECORD(pInt,A,a);
完全展开来后如下:
(A*)((char*)pInt - (unsigned long)(&((A*)0)->a))

为什么要用这个宏:
       这个宏所做的操作其实就是把pInt与A结构中的相应的类型值进行一个位置配对。上面可以看到int a定义在第二个数据中,可以想象,如果我们有a的地址,然后直接把a转化成A*的话,很明显,a的地址就变成了A*的首地址了,但问题是A的第一个元素是char型的,这样的话,pInt就无法对齐上结构中的a元素的位置了。所以要进行一个偏移量操作.

下面,我们下解析一下:
        首先,红色的部分很容易理解,我们知道,如果有一个int *a;的指针,我们a - 1,其实相当于a - sizeof(int),相于于把指针向右移了4个位置,把一个指针转化成一个char*型,这样,进行四则运算时就会按照我们正常的操作,(char*)(a - 1)就只是把指针移动了一个位置。
        然后,看下紫色的部分,首先,要明白,对0指针的取值操作并不会出错,只是不确定这个值返回的是什么值,当然,如果我们对这个值进行修改,这是很危险的。这里用的0位置指针是很特别的,相对于0的位置,0指针对->a的操作,返回的数值取地址值后再转化成unsigned long值,其实得到的就是a相对于结构体A来说,偏移了多少个位置。0是起始地址,那么对于一个->操作,简单来理解,其实相当于0(结构体起始地址)+ sizeof(a前面的数据),当然,这里要考虑字节对齐的问题不过,编译器还是会帮我们把这些都完成。
       最后,我们知道了pInt的结构体的首地址,知道了a的偏移地址,那么我们把pInt的地址值-偏移量,相当于把pInt倒退了偏移量个地址值,然后,我们再转换甩A*的话,相当于A*的起始地址已经是pInt的前面偏移量个地址,也就是a最前面的一个元素的地址值,对于A来说就是char c的地址,这样,我们就得到了正确的起始地址,然后再转换成(A*)的话,我们的pInt就能和A*的a的地址对应上了.

你可能感兴趣的:(c,windows,Class,编译器,DDK)