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