Linux C: container_of()和offsetof()函數與結構體成員的偏移量計算

就內核的container_of()函數,發表一下個人見解。

在內核中,多處地方用到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) );})

作用---根據結構體成員member的地址ptr,求出結構體type的起始地址。

它是如何實現的呢?下面一步一步來拆解。

下面一步一步來拆解。

 

一、offsetof()函數

其中的offsetof()函數,在include/linux/stddef.h中,定義如下:

#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
#endif /* __KERNEL__ */

只需看這行:

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

這是一個宏函數,其作用---求成員MEMBER在結構體TYPE中的偏移

(TYPE *)0這種奇葩寫法是什麼意思呢?

如有以下結構體:

struct demo
{
	char 	c;
	short 	s;
	int 	i;
};

其中各成員的對齊方式如下:(有疑問者請自行了解結構體字節對齊規則)

char - 1字節對齊;short - 2字節對齊;int - 4字節對齊;

結構體在內存中地址圖示(p爲結構體的首地址)

由圖可知:某成員相對結構體的偏移=該成員地址-結構體首地址,例:偏移量(s) = 地址(s) - p

由此,想象將該結構體整體移到0地址處呢?

那麼p=0,成員偏移量=成員的址址,問題秒簡化。

如何將結構體整體移至0地址處?--- 將0地址進行類型強轉換,即(struct demo *)0,這樣是虛擬0地址處有struct demo結構體,只可讀而不可寫,因爲C語言是允許在任何地址進行任何類型轉換,讀動作是沒問題的,而寫動作責要謹慎且後果自負。

由上,成員s的偏移量=s的地址:&((struct demo *)0)->s,再將其強轉化爲size_t類型:

(size_t)&((struct demo *)0)->s;

這樣,是不是就跟offsetof()這個宏函數一樣了。

 

二、container_of()函數

1、邏輯圖示

container_of(ptr, type, member)函數的作用:由結構體成員member的地址ptr,求出結構體type的起始地址。

其參數:

 * @ptr:      指向成員member的指針(或member的址址)
 * @type:    包含成員member的結構體類型
 * @member:    成員的名字

由圖示更好理解:

container_of()函數所實現的就是求出圖中的“?”,即結構體的起始地址。

已知成員member的地址ptr,求結構體的首地址,只需知道ptr與結構體首地址的偏移量,而這恰恰就是offsetof()宏函數了。

那麼,結構體的首地址 = ptr - offsetof(type,member) ,加上類型轉換等優化,變成:

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

就這樣,收工?

 

2、優化/學習/嚴謹

與container_of()原定義有點出入,其多出一行:

const typeof( ((type *)0)->member ) *__mptr = (ptr);

typeof()函數:獲取變量(member)的類型;

這行的意思:定義一個與member同類型的指針__mptr指向ptr,後面用__mptr而不直接用ptr了。

這行好像是多此一舉,並無大用???

原因看這裏:若container_of()的參數ptr有誤,即ptr與member的類型不匹配,編譯時便會提示warnning信息,以引起注意;若無此語句,便無warning引起注意,後果可大可小!!!這便是編程的嚴謹性體現之處了。

 

3、宏定義小知識

注意到container_of()宏函數的外圍是用({ })雙括號包起來的,這有什麼用呢?

首先,要明白宏定義只是在預處理階段作簡單的文本替換。

()小括號:

()的運算優先級高,能將此宏包起來作爲一個整體而不被拆分,以免造成意料之外的影響。一般宏定義含多級運算或多表達式等都建議用()包起來。

 

{}大/花括號:

其作用較複雜,(個人觀點,歡迎糾正)

1、語法要求:假如去掉{},調用container_of()則變成:

(
    const typeof( ((type *)0)->member ) *__mptr = (ptr);
    (type *)( (char *)__mptr - offsetof(type,member) );
)

細數()是否成對出現,發現一對()會被“ ; ”隔斷,猶如:( foo(); ) 這樣,語法上是不允許一對()被“ ; ”隔斷的。加上{}則表示這應當成一個整體(複合語句)。

2、複合語句:{}將多個語句包起來,在程序中應看作爲一條語句執行,且以最後一條語句的執行結果作爲其值,可用於賦值。

3、限定作用域:{}裏面定義的變量只在{}中有效,若無{}且多次調用該宏,則會導致變量被重複定義。

4、形式作用:宏函數最好要有函數的樣子,就是用{}包起來表示,使更像函數。

 

三、結構體偏移量的應用

 結構體中的偏移量相關應用,

1、某成員在結構體中的相對偏移:參考offsetof()函數,巧用0地址指針,如(size_t)&((struct demo *)0)->s,訪問某成員時可避免寫死相對偏移量,便於修改/維護/移植等。

(size_t)&((struct demo *)0)->s        // 結構體某成員在結構體中的偏移量

2、知道結構體某成員的地址,想要得到結構體的首地址:參考container_of(),如內核鏈表中就有應用。

 

希望此博文能對你有積極影響,水平有限,歡迎指點。

 

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