内核链表中offsetof 和container_of的一些理解

发布于:2025-05-13 ⋅ 阅读:(14) ⋅ 点赞:(0)

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 的使用和原理,在内核链表中,会有使用较多,方便以后查看

一下启发来自与这位大佬的博客:

Linux内核链表——看这一篇文章就够了 - Crystal_Guang - 博客园


网站公告

今日签到

点亮在社区的每一天
去签到