我們在書寫C程序的時候,有時候需要根據結構體成員變量的地址,得到結構體的地址,特別是我們想用C來實現C++的繼承特性的時候。
我們對問題的分析如下:
- 輸入:一個結構體定義type,這個結構體中某個成員變量的名字member以及它的地址ptr
- 輸出:包含此成員變量的結構體的地址
為了便於分析,我們給出一個實例來說明
struct father_t { int a; char *b; double c; }f; char *ptr = &(f.b); //而不是 ptr = f.b; 這里ptr是b的地址,而不是它指向的地址。
根據C語言對struct類型的存儲特性,我們可以畫這么一個圖示:
通過分析圖示,我們可以看出,我們只需要把當前知道的成員變量的地址ptr,減去它在結構體當中相對偏移4就的到了結構體的地址(ptr-4)。
在linux當中對此有一個很好的宏可以使用,叫做 container_of, 放在 linux/kernel.h(http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/
)當中。它的定義如下所示:
#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER )
宏功能:獲得一個結構體變量成員在此結構體中的偏移量。
1. ( (TYPE *)0 ) 將零轉型為TYPE類型指針;
2. ((TYPE *)0)->MEMBER 訪問結構中的數據成員;
3. &( ( (TYPE *)0 )->MEMBER )取出數據成員的地址,即相對於0的偏移量,要的就這個;
4.(size_t)(&(((TYPE*)0)->MEMBER))結果轉換類型,size_t應該最終為unsigned int類型。
此宏的巧妙之處在於將 0 轉換成(TYPE*),這樣結構體中成員的地址即為在此結構體中的偏移量。
示例:
#include <stdio.h> #define offsetof(TYPE, MEMBER) ((int)(&((TYPE *)0)->MEMBER)) struct _test_ { char x; int y; float z; }; int main(void) { int temp = -1; temp = offsetof(struct _test_, z); printf("temp = %d\n", temp); return 0; }
運行后結構為:temp = 8。 顯然求出了 結構體成員變量 z 在結構體中的偏移量為 8。
如果是
這個技巧在linux內核里面非常常見,通過常量 地址的強轉得到一個數據類型
如果是char x,輸出0;如果是y,輸出4.
如果不要
(int) 或(size_t)會怎么樣?
編譯不通過,無法將float *類型 轉成float?
因為
&((TYPE *)0)->MEMBER) 得到的是一個指針,不能直接賦值給int 類型。
必須先用(int)強制轉化下。
還有一個與這個有關的宏:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
宏功能:從結構體(type)某成員變量(member)指針(ptr)來求出該結構體(type)的首指針。
對上面的定義,分析如下:
- (type *)0->member為設計一個type類型的結構體,起始地址為0,編譯器將結構體的起始的地址加上此結構體成員變量的偏移得到此結構體成員變 量的偏移地址,由於結構體起始地址為0,所以此結構體成員變量的偏移地址就等於其成員變量在結構體內的距離結構體開始部分的偏移量。即:&((type *)0->member)就是取出其成員變量的偏移地址。而其等於其在結構體內的偏移量:即為:(size_t)(& ((type *)0)->member)經過size_t的強制類型轉換后,其數值為結構體內的偏移量。該偏移量這里由offsetof()求出。
- typeof(((type *)0)->member)為取出member成員的變量類型。用其定義__mptr指針。ptr為指向該成員變量的指針。__mptr為member數據類型的常量指針,其指向ptr所指向的變量處。
- (char*)__mptr轉換為字節型指針。(char*)__mptr - offsetof(type,member))用來求出結構體起始地址(為char *型指針),然后(type*)(char*)__mptr - offsetof(type,member))在(type *)作用下進行將字節型的結構體起始指針轉換為type *型的結構體起始指針。
這就是從結構體某成員變量指針來求出該結構體的首指針。指針類型從結構體某成員變量類型轉換為該結構體類型。
#include <stdio.h> #define offsetof(TYPE, MEMBER) ((int)(&((TYPE *)0)->MEMBER)) #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) struct _test_ { int x; int y; int z; }; void Assignment(struct _test_ *t) { t->x = 1; t->y = 2; t->z = 3; } void GetheadPoint(int *tz) { struct _test_ *p; int temp = -1; p = container_of(tz,struct _test_, z); //根據成員變量的地址獲得該結構體的首地址 temp = p->y; //根據首地址獲得其中另外一個成員變量的值 printf("line31 = %d\n", temp); } int main(void) { int temp = -1; struct _test_ tmp; //定義一個結構體變量 Assignment(&tmp); //給這個變量賦值 GetheadPoint(&tmp.z); //只傳給這個函數一個結構體成員變量的地址 printf("line43 tmp - >x = %d\n", tmp.x); return 0; } 運行結果為: line31 = 2 line43 tmp - >x = 1