container_of分析
時間:2018-09-27 來源:未知
看一個內核非常經典的(de)實現--container_of
這(zhe)個宏(hong)在驅動和內核代碼中(zhong)用的非常(chang)廣泛,下(xia)(xia)面我(wo)們來具體分析下(xia)(xia)。
container_of作用是(shi)通過結構體某個成(cheng)員地(di)址從而拿(na)到整(zheng)個結構體地(di)址。
原型:container_of(ptr, type, member)
示例:現有(you)一個student結構(gou)體變量并初始化。
struct student stu;
stu.num = 1;
stu.score = 100;
strcpy(stu.name, "zhangsan");
現在(zai)我們假設有(you)結構體(ti)某個成員的(de)地址,比如(ru)score成員的(de)地址也就是(shi)&stu.score,那么怎么拿到(dao)整個結構體(ti)的(de)地址呢?
這就是(shi)上面(mian)宏的作用,看下每(mei)個成員分別(bie)代表什么:
ptr:代(dai)表結構體成員的真實地址
type:結構體的類型
member:結構體成員的名字
對于(yu)上面的(de)例子,假如我(wo)們有個函(han)(han)數(shu)參數(shu)需要傳遞score成員的(de)地址,而(er)函(han)(han)數(shu)內部需要打印出(chu)原(yuan)結構體變量(liang)的(de)num的(de)值,
只需要做如下(xia)操作:
//ptr代(dai)表score成員的地址
void func(float *ptr)
{
struct student *tmp; //定(ding)義一(yi)個結構體指(zhi)針
tmp = container_of(ptr, struct student, score); //先(xian)獲(huo)取到(dao)結構(gou)體變量的地址
printf("num = %d\n", tmp->num); //打印下(xia)
}
用起來還(huan)是非常(chang)簡單的,下(xia)面看下(xia)內核(he)經典的實現(xian):
在
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
看(kan)起來(lai)比較復(fu)雜(za),我(wo)們以上面的例子對宏進行替換得(de)到(dao)如下兩句:
const typeof(((struct student *)0)->score) * __mptr = (ptr);
(struct student *)((char *)__mptr - offsetof(struct student, score));
第一句(ju)定義(yi)了(le)一個__mptr指(zhi)針指(zhi)向了(le)ptr,也就是指(zhi)向了(le)score成(cheng)員(yuan)的地址(zhi),
前面的(de)(de)typeof(((struct student *)0)->score)作用(yong)是取得(de)結(jie)構體中score成員的(de)(de)類型(xing),屬于gcc的(de)(de)一個關鍵字(zi)用(yong)法。
第二(er)句(ju)話用(yong)__mptr(score成(cheng)員(yuan)(yuan)的(de)地(di)址(zhi)),減去score成(cheng)員(yuan)(yuan)在原結構體(ti)中的(de)偏移值,就(jiu)得到(dao)了原結構體(ti)變量的(de)地(di)址(zhi)。
第二句中又牽扯到一個新的(de)宏:offsetof,它的(de)作用就是求某個結構(gou)體成(cheng)員(yuan)在(zai)結構(gou)體的(de)偏移值。看下(xia)原型(xing):
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
替換上(shang)面的offsetof(struct student, score)得到結果就是:
((size_t) &((struct student *)0)->score)
非常巧妙,先把0地址轉換為一個(ge)指(zhi)向student的(de)結構體類型的(de)指(zhi)針(zhen),然后取出其score成員的(de)地址,
因(yin)為這個地址是(shi)相對于0地址的,所(suo)以本身值就(jiu)代(dai)表成(cheng)員的偏移(yi)量(liang),size_t是(shi)對地址進行的強轉(zhuan)。
我們再回到container_of的(de)原型,其實它(ta)的(de)第一句話定義新的(de)指針完(wan)全沒有必(bi)要(yao),那么做只是為了規范性(xing),
完(wan)全(quan)可以(yi)改成如下的定義,效(xiao)果(guo)一(yi)樣。
#define container_of(ptr, type, member) ({ \
(type *)( (char *)ptr - offsetof(type,member) );})
也就(jiu)說我們直接用score成員的地址(zhi)減去它的偏移量即可,是不是好(hao)理解多了。
后,來個完(wan)整(zheng)的測(ce)試用例(li):
#include
#include
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
(type *)( (char *)ptr - offsetof(type,member) );})
struct student{
char name[10];
int num;
};
//num只是(shi)num成(cheng)員的地址
void func(int *num_addr)
{
struct student *test;
test = container_of(num_addr, struct student, num);
printf("%s\n", test->name);
}
int main()
{
struct student stu;
strcpy(stu.name, "zhangsan");
stu.num = 3;
func(&stu.num);
}
內(nei)核的list核心鏈(lian)表關鍵的實現就是container_of,理解上面的內(nei)容更有助于我們學(xue)習(xi)list鏈(lian)表。