1. 程式人生 > >linux核心常用巨集 container_of

linux核心常用巨集 container_of

在linux 核心中有一些騷操作的巨集,以下是我對container_of巨集的理解

舉個例子假設有如下結構體

struct test {
    int a;
    int b,
    int c;
} *test1;

一般來說,我有test結構體變數的指標test1,我可以用test->a,test->b,test->c來分別訪問變數a,b,c.

假設我現在知道b變數的地址(指標),能不能找到test結構體變數母體的地址呢?

container_of巨集就是來解決這個問題的,用法如下

#define offsetof(struct_t,member) ((size_t)(char *)&((struct_t *)0)->member)

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


stuct test *result;
int *test_b = &test1->b;

result = container_of(test_b,struct test,b);

result 就是我們獲得的struct test的地址,其實與test1是同一個值。

 

 

下面解析container_of這個巨集

這麼騷的操作的實現其實也只有兩行程式碼哈哈,他主要依賴於gcc c編譯器的內建關鍵字typeof,和offsetof這個巨集

我們就具體問題具體分析對以下這一句進行巨集展開

result = container_of(test_b,struct test,b);

具體分析,首先是第一行程式碼的解析

	const typeof( ((type *)0)->member ) *__mptr = (ptr); \\這裡巨集展開為

        const typeof( ((struct test *)0)->b) *__mptr = test_b; \\進一步展開為
        
        const int *__mptr = test_b; 
         
    

typeof其實就是獲取 結構體成員b的型別,這裡為int型,這句程式碼只是定義了一個變數__mptr,將test_b的值賦予它。當然__mptr的型別和test_b的型別是一致的,都為int *型。

然後是第二行程式碼的解析

(type *)( (char *)__mptr - offsetof(type,member) ); \\這裡巨集展開為

(type *)( (char *)__mptr - ((size_t)(char *)&(type *)0->member) ); \\ 進一步展開為

(struct test *)( (char *)__mptr - ((size_t)(char *)&(struct test *)0->b) ); \\ 進一步計算為


(struct test *)( (char *)__mptr - ((size_t)(char *)(4) ); \\ 進一步計算為

(struct test *)( (char *)__mptr - 4 ); \\ 進一步計算為

(struct test *)( (char *)test_b - 4 ); 

其實最後結果就是test_b的值減去4,然後再強制轉換為struct test *型的指標而已。

比較trick的實現其實只是offsetof這個巨集, 在此處的例子中,0這個地址強制轉換為strcut test*的指標,然後訪問其變數b,再獲取b變數的地址,再其強制轉換為無符號整型size_t.

因為這個struct test結構體地址從0開始,所以b變數的地址就是0+4,這個值了。

 

這個看似複雜的東西其實其本質都還是挺簡單的。