container_of()宏


  

在學習Linux驅動的過程中,遇到一個宏叫做container_of。
該宏定義在include/linux/kernel.h中,首先來貼出它的代碼:

/** 
* container_of - cast a member of a structure out to the containing structure 
* @ptr:        the pointer to the member. 
* @type:       the type of the container struct this is embedded in. 
* @member:     the name of the member within the struct. 
* */ 

#define container_of(ptr, type, member) ({                      \ 
const typeof( ((type *)0)->member ) *__mptr = (ptr);    \ 
(type *)( (char *)__mptr - offsetof(type,member) );})

它的作用顯而易見,那就是
根據一個結構體變量中的一個域成員變量的指針來獲取指向整個結構體變量的指針。比如,有一個結構體變量,其定義如下:

struct demo_struct { 
           type1 member1; 
           type2 member2; 
           type3 member3; 
           type4 member4; 
};      
struct demo_struct demo;
同時,在另一個地方,獲得了變量demo中的某一個域成員變量的指針,比如:
      type3  *memp = get_member_pointer_from_somewhere();
此時,如果需要獲取指向整個結構體變量的指針,而不僅僅只是其某一個域成員變量的指針,我們就可以這麼做:
     struct demo_struct *demop = container_of(memp, struct demo_struct, member3);
這樣,我們就通過一個結構體變量的一個域成員變量的指針獲得了整個結構體變量的指針。
下面說一說我對於這個container_of的實現的理解:
首先,我們將container_of(memp, struct demo_struct, type3)根據宏的定義進行展開如下:
      struct demo_struct *demop = ({                      \ 
         const typeof( ((struct demo_struct *)0)->member3 ) *__mptr = (memp);    \ 
         (struct demo_struct *)( (char *)__mptr - offsetof(struct demo_struct, member3) );})
其 中,typeof是GNU C對標準C的擴展,它的作用是根據變量獲取變量的類型。因此,上述代碼中的第2行的作用是首先使用typeof獲取結構體域變量member3的類型爲 type3,然後定義了一個type3指針類型的臨時變量__mptr,並將實際結構體變量中的域變量的指針memp的值賦給臨時變量__mptr。

(char *)__mptr轉換爲字節型指針。(char *)__mptr - offsetof(type,member) )用來求出結構體起始地址(爲char *型指針),然後(type *)( (char *)__mptr - offsetof(type,member) )在(type *)作用下進行將字節型的結構體起始指針轉換爲type *型的結構體起始指針。

假設結構體變量demo在實際內存中的位置如下圖所示:
     demo
 +-------------+ 0xA000
 |   member1   |
 +-------------+ 0xA004
 |   member2   |
 +-------------+ 0xA010
 |   member3   |
 +-------------+ 0xA018
 |   member4   |
 +-------------+

則,在執行了上述代碼的第2行之後__mptr的值即爲0xA010。
再看上述代碼的第3行,其中需要說明的是offsetof,它定義在include/linux/stddef.h中,其定義如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
先分析一下這個宏的運行機理:
一共4步 
1. ( (TYPE *)0 ) 將零轉型爲TYPE類型指針; 
2. ((TYPE *)0)->MEMBER 訪問結構中的數據成員; 
3. &( ( (TYPE *)0 )->MEMBER )取出數據成員的地址; 
4.(size_t)(&(((TYPE*)0)->MEMBER))結果轉換類型。巧妙之處在於將0轉換成(TYPE*),結構以內存空間首地址0作爲起始地址,則成員地址自然爲偏移地址;
同樣,我們將上述的offsetof調用展開,即爲:
 (struct demo_struct *)( (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) );
可見,offsetof的實現原理如上所述,就是取結構體中的域成員相對於地址0的偏移地址,也就是域成員變量相對於結構體變量首地址的偏移。
因 此,offsetof(struct demo_struct, member3)調用返回的值就是member3相對於demo變量的偏移。結合上述給出的變量地址分佈圖可知,offsetof(struct demo_struct, member3)將返回0x10。
於是,由上述分析可知,此時,__mptr==0xA010,offsetof(struct demo_struct, member3)==0x10。
因此, (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) == 0xA010 - 0x10 == 0xA000,也就是結構體變量demo的首地址(如上圖所示)。
這就是從結構體某成員變量指針來求出該結構體的首指針。指針類型從結構體某成員變量類型轉換爲該結構體類型。
由此,container_of實現了根據一個結構體變量中的一個域成員變量的指針來獲取指向整個結構體變量的指針的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章