container_of 和 sizeof一样,都是编程世界中的小贝壳,但用处颇大,且使用频率很高,特别是在内核中的应用更广泛,下面就来做下介绍。 一、定义: //获取结构体成员相对于结构体的偏移 #define offsetof(TYPE,MEMBER) ((int) &((TYPE *)0)->MEMBER) //通过获取结构体中的某个成员,反推该结构体的指针 #define container_of(ptr, type , member) ({ \ const typeof(((type *)0)->member) *__mptr = (ptr) ; \ (type *)((char *)__mptr - offsetof(type,member)) ;}) 二、作用 container_of的作用是根据结构体中某成员的地址、结构体的类型和成员字段名来求解该结构体的首地址,看下面的解析。 三、解析 const typeof(((type *)0)->member) *__mptr = (ptr) ; 第一阶段: 假定有一个该类型的结构体首地址是0,(type*)0; 然后用typeof求解出该结构体成员member的数据类型, typeof(((type *)0)->member),接着用该类型定义一个变量,__mptr,直接给其赋值传进来的参数ptr; 第二阶段: (char*)__mptr是一个内存中的地址,且是成员字段变量的首地址,那么用这个地址,减去成员字段和结构体地址的偏差就得到了结构体的首地址了,到这一步能理解码?其实,offsetof(type,member)就是求偏差的,来看看它。 第三阶段: #define offsetof(TYPE,MEMBER) ((int) &((TYPE *)0)->MEMBER) 其实地址是四字节的数,而int类型也是四字节,offsetof直接用假定的0地址处的结构体来求的偏差,要知道偏差在同类型的结构体中都是一样的,把结构体地址放在0地址,那么偏差就是成员字段的地址了。 有没有一个疑问? 0地址能用来假定吗?0地址不是内核地址吗? 要知道container_of是一个宏定义,在编译阶段就展开了,0地址对于编译器来说是死的,不是cpu的运行地址,好理解了吧?看下面两段代码! 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;} 一个开卷有益的公众号:IT平头哥 |
|