關於container_of見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) );})
container_of在Linux Kernel中的應用非常廣泛,它用於獲得某結構中某成員的入口地址.
關於offsetof見stddef.h中:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
TYPE是某struct的類型 0是一個假想TYPE類型struct,MEMBER是該struct中的一個成員. 由於該struct的基地址為0, MEMBER的地址就是該成員相對與struct頭地址的位移量.
關於typeof,這是gcc的C語言擴充保留字,用於聲明變數類型.
const typeof( ((type *)0->member ) *__mptr = (ptr);意思是聲明一個與member同一個類型的指標常量 *__mptr,並初始化為ptr.
(type *)( (char *)__mptr - offsetof(type,member) );意思是__mptr的地址減去member在該struct中的位移量得到的地址, 再轉換成type型指標. 該指標就是member的入口地址了.
例一;
container_of宏定義在[include/linux/kernel.h]中:
#define container_of(ptr, type, member) /
const typeof( ((type *)0)->member ) *__mptr = (ptr); /
(type *)( (char *)__mptr - offsetof(type,member) );
offsetof宏定義在[include/linux/stddef.h]中:
#define offsetof(type, member) ((size_t) &((type *)0)->member)
下面用一個測試程式test.c來說明
#include<stdio.h>
struct student{
char name[20];
char sex;
}stu={"zhangsan",'m'};
main()
{
struct student *stu_ptr; //儲存container_of宏的傳回值
int offset; //儲存offsetof宏的傳回值
//下面三行代碼等同於 container_of(&stu.sex,struct student, sex )參數帶入的情形
const typeof(((struct student*)0)->sex) *_mptr = &stu.sex;
//首先定義一個 _mptr指標, 類型為struct student結構體中sex成員的類型
//typeof 為擷取(((struct student*)0)->sex)的類型,此處此類型為char
//((struct student*)0)在offsetof處講解
offset = (int)(&((struct student *)0)->sex);
/*((struct student*)0)為 把 0地址 強制轉化為指向student結構體類型的指標
該指標從地址 0 開始的 21個位元組用來存放name 與 sex(char name〔20〕與 char sex共21位元組)
sex存放在第20個位元組出(從0位元組開始)
&((struct student *)0)->sex 取出sex地址(此處即為20) 並強制轉化為整形
所以offset為20,後面的printf結果將證明這一點*/
stu_ptr = (struct student *)((char*)_mptr - offset);
/*((char*)_mptr - offset)此處先把_mptr指標轉化為字元形指標
(為什麼這麼做呢? 如果_mptr為整形指標 _mptr - offset 相當於減去 sizeof(int)*offset個位元組,這裡是char型的指標 減去一個int值,指標之間不能加減,指標只能和整數進行加減)
減去 offset值 相當於 得到_mptr所在結構體的首地址(即stu的地址)
然後我們把 該地址 強制轉化為 struct student類型即可正常使用了*/
printf("offsetof stu.sex = %d/n",offset);
printf("stu_ptr->name:%s/tstu_ptr->sex:%c/n", stu_ptr->name, stu_ptr->sex);
return 0;
}
例二:
它的作用顯而易見,那就是根據一個結構體變數中的一個域成員變數的指標來擷取指向整個結構體變數的指標。比如,有一個結構體變數,其定義如下:
1. struct demo_struct {
2. type1 member1;
3. type2 member2;
4. type3 member3;
5. type4 member4;
6. };
7.
8. struct demo_struct demo;
同時,在另一個地方,獲得了變數demo中的某一個域成員變數的指標,比如:
1. type3 *memp = get_member_pointer_from_somewhere();
此時,如果需要擷取指向整個結構體變數的指標,而不僅僅只是其某一個域成員變數的指標,我們就可以這麼做:
1. struct demo_struct *demop = container_of(memp, struct demo_struct, member3);
首先,我們將container_of(memp, struct demo_struct, type3)根據宏的定義進行展開如下:
1. struct demo_struct *demop = ({ /
2. const typeof( ((struct demo_struct *)0)->member3 ) *__mptr = (memp); /
3. (struct demo_struct *)( (char *)__mptr - offsetof(struct demo_struct, member3) );})
其中,typeof是GNU C對標準C的擴充,它的作用是根據變數擷取變數的類型。因此,上述代碼中的第2行的作用是首先使用typeof擷取結構體域變數member3的類型為 type3,然後定義了一個type3指標類型的臨時變數__mptr,並將實際結構體變數中的域變數的指標memp的值賦給臨時變數__mptr。
假設結構體變數demo在實際記憶體中的位置如所示:
demo
+-------------+ 0xA000
| member1 |
+-------------+ 0xA004
| member2 |
| |
+-------------+ 0xA010
| member3 |
| |
+-------------+ 0xA018
| member4 |
+-------------+
則,在執行了上述代碼的第2行之後__mptr的值即為0xA010。
再看上述代碼的第3行,其中需要說明的是offsetof,它定義在include/linux/stddef.h中,其定義如下:
1. 24#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
同樣,我們將上述的offsetof調用展開,即為:
1. (struct demo_struct *)( (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) );
可見,offsetof的實現原理就是取結構體中的域成員相對於地址0的位移地址,也就是域成員變數相對於結構體變數首地址的位移。
因此,offsetof(struct demo_struct, member3)調用返回的值就是member3相對於demo變數的位移。結合上述給出的變數地址分布圖可知,offsetof(struct demo_struct, member3)將返回0x10。
於是,由上述分析可知,此時,__mptr==0xA010,offsetof(struct demo_struct, member3)==0x10。
因此, (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) == 0xA010 - 0x10 == 0xA000,也就是結構體變數demo的首地址(如所示)。
由此,container_of實現了根據一個結構體變數中的一個域成員變數的指標來擷取指向整個結構體變數的指標的功能。
轉載自:http://blog.chinaunix.net/u3/94312/showart_1922123.html
1. 我們先寫一個小程式:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 typedef struct {
6 int a;
7 int b;
8 int c;
9 }hehe;
10
11 int main(int argc, char *argv[])
12 {
13 hehe hoho;
14 hehe *haha;
15 hehe *hihi;
16 int *ptr;
17
18 hoho.a = 1;
19 hoho.b = 2;
20 hoho.c = 3;
21
22 hihi = &hoho;
23
24 ptr = &hoho.b;
25
26 printf("ptr = %d, hihi = 0x%08x\n", *ptr, hihi);
27
28 haha = ptr - (&hihi->b - (int *) hihi);
29 printf("a = %d, b = %d, c = %d \n", haha->a, haha->b, haha->c);
30 }
程式中定義了一個結構體hehe,它有3個成員a,b,c;用hehe定義hoho,並定義2個hehe類型的指標haha,hihi;初始化hoho的成員a,b,c分別為1,2,3;hihi指向hoho,ptr指向hoho.b;
這樣我們要做的就是通過hoho的成員b獲得hoho的首地址,也就是28行:
28 haha = (char *)ptr - (char *)(&hihi->b - (int *) hihi);
要想使haha最終的值為hoho的首地址,那麼haha = &hoho = &hoho.b - (&hoho.b - &hoho)
= ptr - hoho.b到hoho的位移
= ptr - (&hihi->b - (int *) hihi)
因此上述程式編譯後執行正確的列印出hoho的a,b,c的值:
$ ./2
ptr = 2, hihi = 0xbfb838dc
a = 1, b = 2, c = 3
2. 接下來我們對這個程式進行修改,從上面的列印結果看到hihi的值為0xbfb838dc,也就是hoho的首地址為0xbfb838dc,而指標其實就 是一個儲存一個地址的記憶體地區,而指標的類型就決定瞭解析這個地址的方法,因此我們就可以用進行了強制類型轉化的地址來代替指標,我想對c語言有所瞭解的 都能明白這種替換的,ok,那麼這裡我們作出下面的修改:
28 haha = ptr - (&((hehe *)(0xbfb838dc))->b - (int *)0xbfb838dc);
因 為hihi為指向一個hehe結構體的指標,因此這裡進行替換的時候需要將hihi->b改為((hehe *)(0xbfb838dc))->b,而hihi就直接0xbfb838dc就可以了,不過這裡需要加上(int *),因為b的類型是 int型的,直接進行這種地址的加減的時候一定要保證兩個地址對應的類型一致,不然程式會出錯。
這樣編譯後運行:
$ ./2
ptr = 2, hihi = 0xbfea6bfc
a = 1, b = 2, c = 3
恩,列印出來的a,b,c的值還是正確的。
3. 你可能會注意到上面的hihi的值已經發生了變化,這是自然的,每次啟動並執行程式分配的記憶體位址都不一定相同,這樣就會發現一個問題,其實這個結構體在記憶體 的什麼位置都不會有影響,而且這裡我們計算的是hoho.b到hoho的位移,因此hoho的首地址的位置不會對結果有影響,那麼就可以有一個結 論&((hehe *)(0xbfb838dc))->b - (int *)0xbfb838dc = &((hehe *)(0x0))->b
- (int *)0x0,因此我們對程式作下面的修改:
28 haha = ptr - (&((hehe *)(0))->b - (int *) 0);
編譯後運行:
$ ./2
ptr = 2, hihi = 0xbf9d071c
a = 1, b = 2, c = 3
結果仍然沒有問題。
4. 我們再研究一下程式的28行:
28 haha = ptr - (&((hehe *)(0))->b - (int *) 0);
可以看到&((hehe *)(0))->b - (int *)0,很明顯我們可以將程式改為:
haha = ptr - &((hehe *)(0))->b
不過,這裡程式必須要加上加上強制類型轉換,不然會計算錯誤,因此程式:
28 haha = (char *)ptr - (char *)&((hehe *)0)->b;
編譯運行之後:
$ ./2
ptr = 2, hihi = 0xbff5fcac
a = 1, b = 2, c = 3
結果仍然正確。
5. 將這個代碼寫成宏的形式,就可以寫成
#define container_of(ptr, type, member) (type *)((char *)ptr - (char *)&((type *)0)->member)
最後將程式改為
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 #define container_of(ptr, type, member) (type *)((char *)ptr - (char *)&((type *)0)->member)
6
7 typedef struct {
8 int a;
9 int b;
10 int c;
11 }hehe;
12
13 int main(int argc, char *argv[])
14 {
15 hehe hoho;
16 hehe *haha;
17 hehe *hihi;
18 int *ptr;
19
20 hoho.a = 1;
21 hoho.b = 2;
22 hoho.c = 3;
23
24 hihi = &hoho;
25
26 ptr = &hoho.b;
27
28 printf("ptr = %d, hihi = 0x%08x\n", *ptr, hihi);
29
30 haha = container_of(ptr, hehe, b);
31 printf("a = %d, b = %d, c = %d \n", haha->a, haha->b, haha->c);
32 }
33
編譯後運行:
$ ./2
ptr = 2, hihi = 0xbfe14b5c
a = 1, b = 2, c = 3
結果正確。
最後你只需要對這跟宏稍作一些修改就可以寫成文章最開始的那種形式了。
恩,以上就是對container_of宏的推導過程,希望會對大家有所協助。