container_of宏分析

1. 背景概要

最近這幾天在忙於研究spi驅動,在看spi核心代碼(spi.c)時,發現一個container_of宏,不甚瞭解,於是深入研究之。這不看不知道,一看嚇一跳啊,一跟進去又給你整個更復雜的宏。好的,廢話不多說,直接上代碼:

/* kernel3.0.15/drivers/spi/spi.c */

/* kernel3.0.15/include/linux/spi/spi.h */


/*kernel3.0.15/include/linux/kernel.h*/


一路追蹤,終於到了container_of宏了,下面慢慢給你道清其原委。

2. container_of宏分析

(1)container_of在Linux內核中是一個常用的宏,用於從包含在某個結構中的指針獲得結構本身的指針,通俗地講就是通過結構體變量中某個成員的首地址進而獲得整個結構體變量的首地址。

(2)container_of宏參數說明

ptr:表示結構體中成員的地址;

type:表示結構體類型;

member:表示結構體中的成員;

(3)container_of宏實現

其實它的語法很簡單,只是一些指針的靈活應用,它分兩步:
第一步,首先定義一個臨時的數據類型(通過typeof( ((type *)0)->member )獲得)與ptr相同的指針變量__mptr,然後用它來保存ptr的值。
說明:typeof是GNU C對標準C的擴展,它的作用是根據變量獲取變量的類型,typeof關鍵字在linux 內核中很常見。
第二步,用(char *)__mptr減去member在結構體中的偏移量,得到的值就是整個結構體變量的首地址(整個宏的返回值就是這個首地址)。

注:offset宏分析見下文。

3. container_of宏使用示例

①正確示例

#include <stdio.h>

#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) );})
struct test_struct {
	int num;
	char ch;
	float f1;
};

int main(void)
{
	struct test_struct *test_struct;
	struct test_struct init_struct = {12,'a',12.3};
	char *ptr_ch = &init_struct.ch;
	
	test_struct = container_of(ptr_ch,struct test_struct,ch);
	printf("test_struct->num = %d\n",test_struct->num);
	printf("test_struct->ch = %c\n",test_struct->ch);
	printf("test_struct->ch = %f\n",test_struct->f1);
	
	return 0;
}

/**
 * 運行結果:
 * test_struct->num = 12
 * test_struct->ch = a
 * test_struct->ch = 12.300000
 */

②錯誤示例

#include <stdio.h> 

#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) );}) 

struct test_struct {
	int num;
	char ch;
	float f1;
}; 

int main(void)
{
	struct test_struct *test_struct;
	char real_ch = 'A';
	char *ptr_ch = &real_ch;
	
	test_struct = container_of(ptr_ch,struct test_struct,ch);
	printf("test_struct->num = %d\n",test_struct->num);
	printf("test_struct->ch = %c\n",test_struct->ch);
	printf("test_struct->ch = %f\n",test_struct->f1);
    
	return 0;
}

/**
 * 運行結果:
 * test_struct->num = 0
 * test_struct->ch = A
 * test_struct->ch = 0.000000
 * 
 * 注意,由於這裏並沒有使用一個具體的結構體變量,所以成員num和f1的值是不確定的。
 */

4. offset宏分析

(1)offset宏實現

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)
對這個宏的分析大致可以分爲以下4步進行講解:
①( (TYPE *)0 ) 0地址強制 "轉換" 爲 TYPE結構類型的指針;
②((TYPE *)0)->MEMBER 訪問TYPE結構中的MEMBER數據成員;
③&( ( (TYPE *)0 )->MEMBER)取出TYPE結構中的數據成員MEMBER的地址;
④(size_t)(&(((TYPE*)0)->MEMBER))結果轉換爲size_t類型。
宏offsetof的巧妙之處在於將0地址強制轉換爲 TYPE結構類型的指針,TYPE結構以內存空間首地址0作爲起始地址,則成員地址自然爲偏移地址。可能有的讀者會想是不是非要用0呢?當然不是,我們僅僅是爲了計算的簡便。也可以使用是他的值,只是算出來的結果還要再減去該數值纔是偏移地址。

(2)offset宏使用示例

#include<stdio.h>

#defineoffsetof(TYPE, MEMBER) ((size_t) &((TYPE *)4)->MEMBER)

struct test_struct {
	int num;
	char ch;
	float f1;
};

int main(void)
{
	printf("offsetof (struct test_struct,num) = %d\n",offsetof(struct test_struct,num)-4);
	printf("offsetof (structtest_struct,ch) = %d\n",offsetof(struct test_struct,ch)-4);
	printf("offsetof (struct test_struct,f1) = %d\n",offsetof(struct test_struct,f1)-4);

	return 0;
}

/**
 * 運行結果:
 * offsetof (struct test_struct,num) = 0
 * offsetof (struct test_struct,ch) = 4
 * offsetof (struct test_struct,f1) = 8
 *
 * 爲了讓大家加深印象,我們在代碼中沒有使用0,而是使用的4,
 * 所以在最終計算出的結果部分減去了一個4纔是偏移地址,當然實際使用中我們都是用的是0。
 * 注:代碼中開始宏定義時4改爲0,在打印時就不用減去4了。
 */


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