利用宏來求結構體成員偏移值

咱們在書寫C程序的時候,有時候須要根據結構體成員變量的地址,獲得結構體的地址,特別是咱們想用C來實現C++的繼承特性的時候。
咱們對問題的分析以下:linux

  • 輸入:一個結構體定義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/spa

)當中。它的定義以下所示:設計

 

#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER )

宏功能:得到一個結構體變量成員在此結構體中的偏移量。指針

1. ( (TYPE *)0 ) 將零轉型爲TYPE類型指針;
2. ((TYPE *)0)->MEMBER 訪問結構中的數據成員;code

3. &( ( (TYPE *)0 )->MEMBER )取出數據成員的地址,即相對於0的偏移量,要的就這個;
4.(size_t)(&(((TYPE*)0)->MEMBER))結果轉換類型,size_t應該最終爲unsigned int類型。
此宏的巧妙之處在於將 0 轉換成(TYPE*),這樣結構體中成員的地址即爲在此結構體中的偏移量。blog

示例:繼承

#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.io

若是不要

(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)的首指針。

對上面的定義,分析以下:

  1. (type *)0->member爲設計一個type類型的結構體,起始地址爲0,編譯器將結構體的起始的地址加上此結構體成員變量的偏移獲得此結構體成員變 量的偏移地址,因爲結構體起始地址爲0,因此此結構體成員變量的偏移地址就等於其成員變量在結構體內的距離結構體開始部分的偏移量。即:&((type *)0->member)就是取出其成員變量的偏移地址。而其等於其在結構體內的偏移量:即爲:(size_t)(& ((type *)0)->member)通過size_t的強制類型轉換後,其數值爲結構體內的偏移量。該偏移量這裏由offsetof()求出。
  2. typeof(((type *)0)->member)爲取出member成員的變量類型。用其定義__mptr指針。ptr爲指向該成員變量的指針。__mptr爲member數據類型的常量指針,其指向ptr所指向的變量處。
  3. (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
相關文章
相關標籤/搜索