Linux 宏定義container_of詳解

在寫Linux驅動的過程中經常是一個結構體套一層結構體,而在某些函數中傳入的參數是子結構體指針,但是我們又需要獲取的其外層結構體的數據,Linux爲我們提供了container_of宏定義來爲我們解決這個問題。
container_of宏定義就是用來通過內層結構體的指針獲取外層結構體指針,宏定義很巧妙,我等凡人想不出來。下面就詳細分析其實現原理吧。

container_of宏定義需要用到offsetof宏,現在offsetof宏的功能已經變成一個GCC的內建函數了__builtin_offsetof (TYPE, MEMBER),但offsetof宏實際定義可由第二行定義實現。

#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)

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

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

offsetof(TYPE, MEMBER)的操作過程:
首先分析offsetof宏,其功能是獲得成員MEMBERTYPE結構中的偏移量。

  • 宏參數TYPE:表示MEMBER成員所在結構體的數據類型;
  • 宏參數MEMBER:TYPE結構體的數據成員MEMBER;
  • 根據運算符的優先級最內層的((TYPE *)0):表示將0地址轉換爲TYPE類型的指針
  • ((TYPE *)0)->MEMBER:通過0地址,類型爲TYPE的指針,指向成員MEMBER
  • &((TYPE *)0)->MEMBER:對MEMBER成員進行取地址運算,那麼地址值就是MEMBER成員在TYPE結構中的偏移量
  • (size_t)&((TYPE *)0)->MEMBER:將地址值強制轉爲size_t類型的整形數值,即偏移量。

container_of(ptr, type, member)的操作過程:

  • 首先說明關鍵字typeof,可以獲得一個數據的類型
  • 宏參數ptr:表示指向member成員的指針;
  • 宏參數type:表示member成員所在結構體的數據類型;
  • 宏參數member:type結構體的數據成員member;
  • container_of宏被封裝成一個語句塊,包含兩條語句,define預定義返回最後一句話的值
  • 第一句:const typeof( ((type *)0)->member ) *__mptr = (ptr);
  • 根據運算符的優先級最內層的((type *)0):表示將0地址轉換爲type類型的指針
  • ((type *)0)->member:將0地址指針指向member成員
  • typeof( ((type *)0)->member ):獲得member的數據類型
  • const typeof( ((type *)0)->member ) *__mptr:定義一個__mptr指針變量,指針數據類型爲member的數據類型
  • const typeof( ((type *)0)->member ) *__mptr = (ptr);:將宏參數變量ptr賦值給變量__mptr;此時變量__mptr中保存的是member成員的地址
  • 第二句:(type *)( (char *)__mptr - offsetof(type, member) );
  • __mptr強制轉換爲char*指針
  • ( (char *)__mptr - offsetof(type, member) ):用強制轉換後的指針值減去membertype中的偏移量,得到的值就是member成員所在結構體的地址,此時仍然是char*指針
  • (type *)( (char *)__mptr - offsetof(type, member) );將最後的得到的地址轉換爲(type *)指針,即得到member成員所在結構體的指針。

仔細分析會發現,其實根本沒有必要定義第一句,只需一句就可以實現:

#define container_of(ptr, type, member) ({ \

(type *)( (char *)(ptr) - offsetof(type,member) );})

但是爲什麼會定義第一句呢?其實主要爲了對ptrmember做類型檢查,如果用typeof求出來的類型和ptr不一致,那麼在賦值操作時,編譯器會報錯。
可能會有仁兄有疑問:0地址訪問數據,不會出現非法地址訪問的錯誤嗎?
仔細分析,這裏並沒有發生0地址的附近數據的訪問,仔細看看僅僅發生了&取地址、typeof取類型操作,因此不會存在非法訪問內存的問題。

新的內核對此又有了新的定義:

/* Are two types/vars the same type (ignoring qualifiers)? */
#ifndef __same_type
# define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))                  
#endi

#define container_of(ptr, type, member) ({                \
    void *__mptr = (void *)(ptr);                    \
    BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&    \
             !__same_type(*(ptr), void),            \
             "pointer type mismatch in container_of()");    \
    ((type *)(__mptr - offsetof(type, member)));

有2個新變化:
第一,用void *取代了char *來做減法,
第二,用__same_type宏來做類型檢測,錯了有明確的信息提示。

好了,以後繼續把鏈表相關的操作也搬上來。

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