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的地址對應上了.

發佈了57 篇原創文章 · 獲贊 3 · 訪問量 73萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章