内核链表中offsetof 和container_of的一些理解
1,源码
struct home {int a;short b;char c;long f;
};#ifndef offsetof#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif#ifndef container_of
#define container_of(ptr, type, member) \({ \const __typeof__(((type *) NULL)->member) *__mptr = (ptr); \(type *) ((char *) __mptr - offsetof(type, member)); \})
#endif
2 ,offsetof的理解
offsetof:type —— 是最大结构体的类型,即:struct homemember —— 是其中的某个成员的名称,即 f所以求结构体成员f的偏移量可以这样使用:size_t len = offsetof( struct home, f);void show_per(struct home *p)
{printf("a: %u\n", (unsigned int)&p->a);printf("b: %u\n", (unsigned int)&p->b);
}int offsetof_test()
{int len = 0;struct home per;memset( &per, 0, sizeof(struct home));per.a = 10;per.b = 11;per.c = 'A';per.f = 12;show_per(&per);show_per(NULL);len = offsetof( struct home, b);char *pp = &per;printf("len = %d\n", len);printf("b = %d\n", *(&per + len));printf("b = %d\n", *(pp + len));
}
解析:
先看show_per,在调用这个函数中,传递了&per时,正确打印了a,b成员的地址,当我传递NULL时,按道理程序应该出问题的,但是却打印了0,4,这不就是偏移量么!!!
原来:
在编译器中,结构体是存在地址对齐的,即空间为最小成员的最小整数倍,其次当我们使用->或‘.’访问成员时,编译器也是通过偏移量来访问成员变量的,如下:
从图中可以看出,当p为空时,首地址就是0,第二个成员为b(即:age),考虑字节对齐,所以第二个成员的偏移量就是4(int).
然后我们看最后3个打印: 4, 0 , 11.在第二个b = 0,跟我们的想法不一样也,我的想法是,最后两个b的值都是一样的为11,为什么?
printf("b = %d\n", *(&per + len));
这个语句中取的per的地址,然后偏移len的长度,我们很自然想到首地址偏移4,不就是b成员的地址码,但是这里的per的类型是struct home,在它的基础上偏移4,就是移动了4*sizeof(struct home)的长度的地址,但这里并没有报错,打印为0,
解决方法:
将这个地址值赋值给char *,然他一字节的移动,况且偏移量也是以字节为单位的。完美解决
3,container_of的理解
container_of(ptr, type, member) ptr -- 结构体某个成员的地址type -- 该结构体成员的类型member -- 结构体成员的名称作用:找某个成员所在的结构体的首地址//测试代码int container_of_test()
{struct home per;struct home *pper = NULL;memset( &per, 0, sizeof(struct home));per.a = 10;per.b = 11;per.c = 'A';per.f = 12;//赋值的是该成员的地址//long *p = NULL;//p = &per.f;char *p = &per.f;//这个宏计算出这个f成员所在结构体的首地址,赋值给pper,//所以我后面能够打印所有成员,p的地址一定要是其中某个成员本身的地址pper = (struct home *)container_of( p, struct home, f);printf("a = %d,b = %d, c = '%c'\n", pper->a, pper->b, pper->c);}
结果测试:
从结果来看,确实通过container_of,打印了正确的成员变量的值。
container_of 第一行代码用来赋值和产生编译时结构体类型不匹配警告的,
第二行就是求首地址了:
因为ptr的地址就是per.f的地址 — f成员在结构体中的offset ,那就得到了该结构体的首地址了。
所以后面我们可以正确打印这些成员。
4,总结
介绍了offsetof 和 container_of 的使用和原理,在内核链表中,会有使用较多,方便以后查看
一下启发来自与这位大佬的博客:
Linux内核链表——看这一篇文章就够了 - Crystal_Guang - 博客园