container_of() offsetof()函数个人理解

这是在linux-source-4.13.0/include/linux/kernel.h中container_of()函数的定义:

#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))); })

ptr:表示结构体中member的地址
type:表示结构体类型

member:表示结构体中的成员

更多的版本是这样的:

#define container_of(ptr, type, member) ({      \   
 const typeof( ((type *)0)->member ) *__mptr = (ptr);    \  
  (type *)( (char *)__mptr - offsetof(type,member) );})  
他们之间的区别就是__mptr的类型不同一个是void* 一个是type *。

不难发现其中还涉及一个offsetof()函数,这也是一个宏函数,定义在 linux/stddef.h中

#define offsetof(TYPE, MEMBER)  ((size_t)&((TYPE *)0)->MEMBER)

首先先去理解offsetof这个宏函数。刷了很多博客我才得以理解。

(TYPE *)0 :即假设存在一个虚拟地址0,将该地址强制转换成为该结构体指针类型(TYPE *)0。那么地址0开始到sizeof(TYPE)-1长度的内存区域就可以视为一个结构体的内存。0即是虚构的这个结构体的首地址,后面跟着的就是虚构的结构体的成员,尽管这些成员并不具备实际的内容,但是他们之间的地址构造和普通赋值结构体是一样的。

我们利用这个虚构的一段结构体地址只是用来求其成员相对于结构体首地址的偏移量。选择“0”作为虚拟地址也是为了方便,这样每个成员所在这段虚拟内存地址的位置就是它的偏移量。当然也可以设置为其他值,只不过最后要进行减法运算才是真正的偏移量。

以下通过一个程序简单演示以下:

代码清单:

#include <stdio.h>
#define offsetof(TYPE,MEMBER)((int)&((TYPE *)0)->MEMBER)
struct unit
{
	char a;
	short b;
	int c;
	double d;
	
};
int main(int argc, char const *argv[])
{
	struct unit unit;
	printf("unit结构体所占字节数%d\n",sizeof(struct unit) );
	int offset1=offsetof(struct unit,a);
	int offset2=offsetof(struct unit,b);
	int offset3=offsetof(struct unit,c);
	int offset4=offsetof(struct unit,d);
	printf("unit结构体成员a的偏移地址为%d\n",offset1 );
	printf("unit结构体成员b的偏移地址为%d\n",offset2 );
	printf("unit结构体成员c的偏移地址为%d\n",offset3 );
	printf("unit结构体成员d的偏移地址为%d\n",offset4 );
	return 0;
}

最后的输出结果:

由此也可以推出结构体unit的内存存储大致结构

char占一个字节 short占两个字节 int占4个字节 double占8个字节   null部分不存储内容 只是为了实现字节对齐多出来的

整个空间共占16个字节,也说明了为什么sizeof(unit)为16。

再来理解container_of()函数实现。

这个函数是用来根据某个具体结构体的某个具体成员来获取这个结构体的首地址。

无论哪个版本第一步就是创建一个新的指针变量 __mptr 并将其赋值为 ptr(具体结构体成员的地址)

然后在通过这个__mptr减去该结构体成员的偏移量(由offsetof函数计算得出的结果)就是这个具体结构体的具体首地址。再将其强转成我们需要的结构体类型。

同样也使用一个实例来演示一下:

代码清单:

#include <stdio.h>
#define offsetof(TYPE,MEMBER)((int)&((TYPE *)0)->MEMBER)
#define container_of1(ptr, type, member) ({\
        void *__mptr = (void *)(ptr);\            
        ((type *)(__mptr - offsetof(type, member)));})
#define container_of2(ptr, type, member) ({\
		const typeof( ((type *)0)->member ) *__mptr = (ptr);\
		((type *)(__mptr - offsetof(type, member)));})
struct unit
{
	char a;
	short b;
	int c;
	double d;
	
};
int main(int argc, char const *argv[])
{
	struct unit *unit_by_container1;//根据container_of1函数来获取,成员选择a
	struct unit *unit_by_container2;//根据container_of2函数来获取,成员选择a
	struct unit *unit_by_container3;//根据container_of1函数来获取,成员选择b
	struct unit unit_test={'x',1,2,3.3};
	char *unit_a=&unit_test.a;
	short *unit_b=&unit_test.b;
	int *unit_c=&unit_test.c;
	double *unit_d=&unit_test.d;
	unit_by_container1=container_of1(unit_a,struct unit,a);
	unit_by_container2=container_of2(unit_a,struct unit,a);
	unit_by_container3=container_of1(unit_b,struct unit,b);
	printf("unit_by_container1->b为%u\n",unit_by_container1->b );
	printf("unit_by_container1->c为%d\n",unit_by_container1->c );
	printf("unit_by_container1->d为%f\n",unit_by_container1->d );
	printf("unit_by_container2->b为%u\n",unit_by_container2->b );
	printf("unit_by_container2->c为%d\n",unit_by_container2->c );
	printf("unit_by_container2->d为%f\n",unit_by_container2->d );
	printf("unit_by_container3->a为%c\n",unit_by_container3->a );
	printf("unit_by_container3->c为%d\n",unit_by_container3->c );
	printf("unit_by_container3->d为%f\n",unit_by_container3->d );
	return 0;
}

运行结果:

可以发现之前说的两个container_of版本都达到一样的效果。

由此相信可以对这两个函数有了一个比较深刻的理解了。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值