最近在學習c語言宏編程,看到了container_of宏,深入學習了一天,做個筆記留念。
1、看一下書上寫的container_of的版本:
#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)); })
2、舉一個實例:
int main(int argc, char *argv[]) { struct test{ int num; char ch; }t1={100,'c'}; char *pch=&t1.ch; struct test *ptt=container_of(pch,struct test,ch); printf("num=%d\n",ptt->num); return 0; }
替換后的結果:
int main(int argc, char *argv[]) { struct test{ int num; char ch; }t1={100,'c'}; char *pch=&t1.ch; struct test *ptt=({ const typeof(((struct test *)0)->ch) *__ptmp=(pch); (struct test *)((char *)__ptmp - ((size_t) &((struct test *)0)->ch)); }); printf("num=%d\n",ptt->num); return 0; }
如果替換后的結果你還能看懂,說明你是真明白了,呵呵,有沒有興趣自己寫一遍替換后的代碼?
3、多余的不說了,網上有的是講解的,這里就說二點:
1、container_of宏第一步是做類型檢查的,也就是檢查ptr是否是指向結構成員member的,如果我們用typeof求出來的類型和ptr不一致,那么編譯器會報錯。為啥要做這個檢查呢?因為ptr和member都是人工輸入的參數,宏要保證它們是結構體成員和其相關聯的指針,這個宏才有意義,所以類型檢查是必須的。
2、第二步相減時,把mptr指針強轉成(char *)是因為,char指針減法只移一個字節,如果這樣才可能得出准確的地址,否則,改為int類型,在減1就移動4個就亂了。
4、我有研究了最新4.12kernel的該宏,發現有了變化:
/** * 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) ({ \ void *__mptr = (void *)(ptr); \ BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \ !__same_type(*(ptr), void), \ "pointer type mismatch in container_of()"); \ ((type *)(__mptr - offsetof(type, member)));
哇,這里有出現一個新宏:__same_type,趕緊用ctags查一下定義,在include/linux/compiler.h中:
/* Are two types/vars the same type (ignoring qualifiers)? */ #ifndef __same_type # define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b)) #endi
有2個新變化,顯得更加高大上了,第一,用void *取代了char *來做減法,第二,用__same_type宏來做類型檢測,顯得更加的直觀明了,錯了還可以有提示。