看一個內核非常經典的實現--container_of
這個宏在驅動和內核代碼中用的非常廣泛,下面我們來具體分析下。
container_of作用是通過結構體某個成員地址從而拿到整個結構體地址。
原型:container_of(ptr, type, member)
示例:現有一個student結構體變量并初始化。
struct student stu;
stu.num = 1;
stu.score = 100;
strcpy(stu.name, "zhangsan");
現在我們假設有結構體某個成員的地址,比如score成員的地址也就是&stu.score,那么怎么拿到整個結構體的地址呢?
這就是上面宏的作用,看下每個成員分別代表什么:
ptr:代表結構體成員的真實地址
type:結構體的類型
member:結構體成員的名字
對于上面的例子,假如我們有個函數參數需要傳遞score成員的地址,而函數內部需要打印出原結構體變量的num的值,
只需要做如下操作:
//ptr代表score成員的地址
void func(float *ptr)
{
struct student *tmp; //定義一個結構體指針
tmp = container_of(ptr, struct student, score); //先獲取到結構體變量的地址
printf("num = %d\n", tmp->num); //打印下
}
用起來還是非常簡單的,下面看下內核經典的實現:
在
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
看起來比較復雜,我們以上面的例子對宏進行替換得到如下兩句:
const typeof(((struct student *)0)->score) * __mptr = (ptr);
(struct student *)((char *)__mptr - offsetof(struct student, score));
第一句定義了一個__mptr指針指向了ptr,也就是指向了score成員的地址,
前面的typeof(((struct student *)0)->score)作用是取得結構體中score成員的類型,屬于gcc的一個關鍵字用法。
第二句話用__mptr(score成員的地址),減去score成員在原結構體中的偏移值,就得到了原結構體變量的地址。
第二句中又牽扯到一個新的宏:offsetof,它的作用就是求某個結構體成員在結構體的偏移值。看下原型:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
替換上面的offsetof(struct student, score)得到結果就是:
((size_t) &((struct student *)0)->score)
非常巧妙,先把0地址轉換為一個指向student的結構體類型的指針,然后取出其score成員的地址,
因為這個地址是相對于0地址的,所以本身值就代表成員的偏移量,size_t是對地址進行的強轉。
我們再回到container_of的原型,其實它的第一句話定義新的指針完全沒有必要,那么做只是為了規范性,
完全可以改成如下的定義,效果一樣。
#define container_of(ptr, type, member) ({ \
(type *)( (char *)ptr - offsetof(type,member) );})
也就說我們直接用score成員的地址減去它的偏移量即可,是不是好理解多了。
后,來個完整的測試用例:
#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只是num成員的地址
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);
}
內核的list核心鏈表關鍵的實現就是container_of,理解上面的內容更有助于我們學習list鏈表。