語言的結構體可以將不同類型的對象聚合到一個對象中,在內存中,編譯器按照成員列表順序分別為每個結構體變量成員分配內存,但由於 C 的內存對齊機制以及不同機器間的差異,各個成員之間可能會有間隙,所以不能簡單的通過成員類型所占的字長來推斷其它成員或結構體對象的地址。
如果要計算結構體中某成員相對於該結構體首地址的偏移量,一般第一個反應就是該成員的地址與結構體對象的首地址之間的字節數,就比如我定義了這樣一個結構體類型:
typedef struct list_node { int ivar; char cvar; double dvar; struct list_node *next; } list_node;
就用這個類型來定義一個變量:list_node ln; 假設現在求 ln.dvar 的地址與 ln 的地址之間相差多少個字節,用這個表達式:(char *)&ln.dvar - (char *)&ln 即可求出,這個式子的原理是,用 & 符取到 ln.dvar 和 ln 的地址,強制將其轉換為 char * 型並將二者相減,之所以要轉換為 char * 型,是因為 char 型變量占一個字節,根據指針減法的特性,我們要求二者之間的字節數,當然要以單個的字節為單位。好了,求偏移量的基本原理就是這個,那么問題來了,假如你在調用函數的時候,只通過參數傳入了某個結構體成員,函數中並沒有該結構體對象本身,這樣子上述方法就不管用了,那該怎么辦?我們從另一個角度想問題,如果說 B - A 的差就是偏移量,那如果令 A 等於 0,那么 B 它本身的值是不是就是偏移量了?基於這個思路,我們來看看偉大的 Linux 內核是怎樣實現求偏移量的操作的:
/* * 選自 linux-2.6.7 內核源碼 * filename: linux-2.6.7/include/linux/stddef.h */ #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
linux 中是定義了一個宏 offsetof,這個宏接受兩個參數,一個 TYPE 代表結構體的類型,另一個 MEMBER 代表結構體中的成員,我們看看后面的宏替換部分發生了什么,先看 ((TYPE *)0) 這個部分,它把 0 這個數字強制轉換成 TYPE * 型的指針類型,這樣 ((TYPE *)0) 這個整體就相當於一個指針指向了 0 這個地址,不管 0 這個地址是否合法,是否真的有這么一個結構體對象,它都會把以 0 地址為首的一片連續內存當成一個結構體對象操作,那么再看 ((TYPE *)0)->MEMBER 這個部分,((TYPE *)0) 這個指針要取結構體對象中的 MEMBER 成員,因為這只是讀內存的操作,並沒有寫入數據,所以雖說地址不合法,但並不會發生段錯誤,這樣取到 MEMBER 成員后,前面的 & 符就可以對 MEMBER 成員取地址了,剛才我也說了,B - A 的差是偏移量的話,如果 A 等於 0,那么 B 本身就是偏移量,那正好對應現在的情況,((TYPE *)0) 本身就是以 0 地址為首進行操作,那么它取到的 MEMBER 成員所在的地址就是相對於結構體首地址的偏移量,然后再把這個地址強制轉換成 size_t 類型,於是該成員的偏移量就得到了。有沒有感覺很精妙。
知道成員偏移量了,那么求結構體對象本身的地址不就易如反掌了嗎,成員的地址減去偏移量不就是結構體對象的首地址了嗎,基於這個思路,我們再看看偉大的 Linux 是怎樣實現的:
/* * 選自 linux-2.6.7 內核源碼 * filename: linux-2.6.7/include/linux/stddef.h */ #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
它同樣是定義了一個宏,這個宏接受 3 個參數,第一個 ptr 代表已知成員的地址,第二個 type 代表結構體的類型,第三個 member 代表已知的成員,在宏替換的部分,它用到了一個 typeof 關鍵字,((type *)0)->member 這部分和上面講的一樣,用於獲取假設於 0 地址的結構體對象的 member 成員,然后用 typeof 獲取了 member 成員的類型,用該類型定義了一個相應的指針變量 __mptr 並將其賦值為 ptr,然后 ( (char *)__mptr - offsetof(type,member) ) 這部分就是將 __mptr 強制轉換為 char * 型,再減去 member 成員的偏移量就得到了該結構體對象的首地址,再把這個首地址強制轉換為 type * 型也就是結構體本身的類型對應的指針類型,最終該結構體對象的地址就求出來了。
原文博客:http://blog.csdn.net/zhanshen2015/article/details/51500757