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 home
member —— 是其中的某个成员的名称,即 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 的使用和原理,在内核链表中,会有使用较多,方便以后查看
一下启发来自与这位大佬的博客: