在EFI中常常遇到下面這個宏,他的作用是
//
// CONTAINING_RECORD - returns a pointer to the structure
// from one of it's elements.
//
#define _CR(Record, TYPE, Field) ((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))
這個宏的作用是根據一個結構體成員變量的的地址獲得該結構體基地址:
分析~~~
宏前面一段((TYPE *) ((CHAR8 *) (Record)表示成員b的當前地址。
先看關鍵的後面這段 (CHAR8 *) &(((TYPE *) 0)->Field))),表示成員變量B距離結構體首地址的偏移量offset
ANSI C標準允許任何值爲0的常量被強制轉換成任何一種類型的指針,並且轉換結果是一個NULL指針,因此((TYPE *)0)的結果就是一個類型爲TYPE *的NULL指針,如果利用這個NULL指針來訪問TYPE 的成員當然是非法的,但&(((TYPE *)0)->Field)的意圖並非想存取Field字段內容,而僅僅是計算當結構體實例的首址爲((TYPE *)0)時TYPE 字段的地址。編譯器根本就不生成訪問Field的代碼,而僅僅是根據TYPE類型Struct的內存佈局和結構體實例首址在編譯期計算這個(常量)地址,這樣就完全避免了通過NULL指針訪問內存的問題。又因爲首址的值爲0,所以這個地址的值就是字段相對於結構體基址的偏移。“&”取地址符號在這裏取的是->Field 成員的地址,而他的地址肯定是相對於結構體開始位置的偏移量了。
我們來分析一個具體的例子:
typedef struct {
UINT16 A;
UINT16 B;
UINT32 C;
} DATA;
DATA Private_data ={a,b,c};
圖示如下:
已知道的條件是成員b的地址實際地址add1,如何得到Private_data 的地址add0呢?
我們知道數據在內存中是按照小地址往大地址裏面純的 所以 add0=add1-成員b相對於結構體首地址的偏移量offset。
代入已知變量到宏裏面去
#define _CR(Record, TYPE, Field) ((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))
add0 = _CR(add1, struct DATA
, B) ((TYPE *) ((CHAR8 *) (add1) - (CHAR8 *) &(((struct
DATA *) 0)->B)))