container_of 和 offsetof 宏详解
在linux內核鏈表中,會遇到兩個宏。
在include/linux/stddef.h中,有這樣的定義
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
這里的TYPE表示某個結構體類型,MEMBER表示結構體中的一個成員,這個宏求出了成員在結構體中的位置偏移(以字節為單位)
如果你還不理解,我們舉個例子吧。
struct student {char name[20];unsigned char age; };int main(void) {int off = offsetof(struct student, age);printf("off = %d\n",off); } 運行結果是off = 20
其實宏里面的0只是一種特殊情況而已。對這個宏的解釋是:假設結構體處于0x1234這個地址,
((TYPE*)0x1234 )-> MEMBER
->比類型轉換的優先級高,所以要加括號。上面就得到了成員,再取地址,得到成員的地址
&((TYPE*)0x1234 )-> MEMBER
不用加括號,因為&的優先級比較低
再來一個類型轉換
(size_t)&((TYPE*)0x1234 )-> MEMBER
終于得到成員的起始地址了,還沒有完,既然算偏移,就要減去結構體的起始地址
(size_t)&((TYPE*)0x1234 )-> MEMBER ?- 0x1234
不難看出,這里的0x1234換成什么數字都可以,因為偏移是和起始地址無關的。
不信的話可以把這個宏定義改一改,再測試一下
#define offsetof(TYPE, MEMBER) ( (size_t) &((TYPE *)0x2222)->MEMBER - 0x2222 )struct student {char name[20];unsigned char age; };int main(void) {int off = offsetof(struct student, age);printf("off = %d\n",off); } 改成0x2222后,結果還是20.既然什么數字都可以,那就改成0吧,于是后面的-0就可以省略了。于是就得到了開頭的那個宏。
下面我們說另外一個宏。
/**
827? * container_of - cast a member of a structure out to the containing structure
828? * @ptr:? ? the pointer to the member.
829? * @type: ? the type of the container struct this is embedded in.
830? * @member: the name of the member within the struct.
831? *
832? */
833 #define container_of(ptr, type, member) ({? ? ? ? ? \
834 ? ? const typeof( ((type *)0)->member ) *__mptr = (ptr);? ? \
835 ? ? (type *)( (char *)__mptr - offsetof(type,member) );})
繼續舉例子。
struct student {char name[20];unsigned char age; };int main(void) {struct student stu = {"wangdong",22};printf("&stu = %p\n",&stu);printf("&stu.age = %p\n",&stu.age);struct student *p = container_of(&stu.age, struct student, age);printf("p= %p\n",p); }運行結果為&stu = 0x7fff53df9c40
&stu.age = 0x7fff53df9c54
p= 0x7fff53df9c40
第一行和第三行的值是一致的。如果你不理解這個宏的定義,我們先簡化一下它,這樣寫 #define container_of(ptr, type, member) ? ? ? ? ? ? ( ?(type *)( (char *)ptr - offsetof(type,member) ) ?)
(char *)ptr 成員的地址
offsetof(type,member) ?成員的偏移
二者相減,就是結構體的起始地址,最后再加個強制類型轉換。 可是為什么不是這樣寫的呢,而是要多出來一行
const typeof( ((type *)0)->member ) *__mptr = (ptr);
沒有這行到底行不行,其實也行,用上面的例子,去掉這行,也可以得到一樣的結果。
先看看這行什么意思吧,?((type *)0)->member 這是成員,typeof( ((type *)0)->member ) 得到了成員的類型,假設就是unsigned char類型,
const typeof( ((type *)0)->member ) *__mptr 定義了一個這個類型的指針
const unsigned char * __mptr = (ptr); 賦值給定義的這個指針。
我苦思冥想,又結合網上的資料,認為這樣寫是做了一個類型檢查。如果有了這行,假設結構體就沒有member這個成員,那么編譯會報錯。
所以這樣寫保證了member確實是type的一個成員。
還有,這樣寫也保證了ptr確實是這個成員類型的指針,如果不是,編譯也會報錯。再做個實驗。
把剛才的代碼改一下
struct student *p = container_of((int *)&stu.age, struct student, age);
故意把&stu.age轉換成int*,編譯就會報警告:
test.c:33:22: warning: incompatible pointer types initializing 'const typeof (((struct student *)0)->age)
? ? ? *' (aka 'const unsigned char *') with an expression of type 'int *' [-Wincompatible-pointer-types]
? ? ? ? struct student *p = container_of((int *)&stu.age, struct student, age);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.c:11:39: note:expanded from macro 'container_of'
? ? ? ? const typeof( ((type *)0)->member ) *__mptr = (ptr);? ? \
?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ^? ? ? ? ~~~~~
如果沒有這一行,那么就不會報錯。
所以,作者的出發意圖是千方百計地讓程序員寫出安全的代碼啊!真的是用心良苦。
(完)
總結
以上是生活随笔為你收集整理的container_of 和 offsetof 宏详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用双向链表实现一个栈
- 下一篇: 2020年数字营销与商业增长白皮书