offsetof宏与container_of宏分析详解

一.概述

  offsetof宏与container_of宏在linux内核中有着十分广泛的应用。这两个宏也有着非常强大的功能,其中offsetof宏是用来获取结构体某个变量相对于结构体首地址的偏移量;container_of宏是用来根据结构体成员变量地址推出结构体变量首地址。另外在分析这两个宏的实现过程也很有意义,可以帮助我们理解C语言对内存的操作。下面结合具体的代码来分析其实现流程。

二.offsetof宏

<1>offset宏的作用是:用来计算结构体中某个元素和结构体首地址的偏移量(其实质是通过编译器来帮我们计算的)。
<2>offset宏原理:我们虚拟一个type类型结构体变量,然后用type.member的方式来访问那个member元素,继而得到member相对于整个变量首地址的偏移量。
<3>offset宏表达式:

//使用offset宏获取结构体变量的偏移地址,其中:TYPE表示结构体类型名,MEMBER表示成员变量名
#define offsetof(TYPE,MEMBER) ((int)&((TYPE *)0)->MEMBER)

1.示例代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//使用offsetof宏获取结构体变量的偏移地址,其中:TYPE表示结构体名,MEMBER表示成员变量名
#define offsetof(TYPE,MEMBER) ((int)&((TYPE *)0)->MEMBER)

//64位操作系统,默认8字节对齐
struct student
{
	char a;      //8(4+4)
	double b;    //8
	int c;       //8(4+4)
};

int main(int argc,char *argv[])
{
	struct student stu1 = {0};
	stu1.b = 11.22;
	printf("&stu1 = %p\n",&stu1);   	//结构体起始地址
	printf("&stu1.a = %p\n",&stu1.a);	//成员a的地址
	printf("&stu1.b = %p\n",&stu1.b);	//成员b的地址
	printf("&stu1.c = %p\n",&stu1.c);	//成员c的地址
	int offset_a = offsetof(struct student,a);   //获取成员变量a的偏移地址
	int offset_b = offsetof(struct student,b);   //获取成员变量b的偏移地址	
	int offset_c = offsetof(struct student,c);   //获取成员变量c的偏移地址	
	printf("offset_a = %d\n",offset_a);
	printf("offset_b = %d\n",offset_b);
	printf("offset_c = %d\n",offset_c);
	printf("stu1.b = %lf\n",*(double*)((char*)&stu1 + offsetof(struct student,b)));
	return 0;
}

2.结果展示

  由于在linux环境下实现,电脑是64位操作系统,默认8字节对齐,因此成员变量a,b,c的地址偏移分别为0,8,16。结合理论分析看看实际程序执行结果如下:

&stu1 = 0x7ffd4d101490
&stu1.a = 0x7ffd4d101490
&stu1.b = 0x7ffd4d101498
&stu1.c = 0x7ffd4d1014a0
offset_a = 0
offset_b = 8
offset_c = 16
stu1.b = 11.220000

发现没,和我们分析的结果一毛一样!

3.offsetof宏实现原理分析

我们来尝试分析((int)&((TYPE *)0)->MEMBER):
(1)(TYPE *)0 ----------------> (struct student *)0
  这是一个强制类型转换,把0地址强制类型转换成一个指针,这个指针指向一个TYPE类型的结构体变量。实际上这个结构体变量可能不存在,但是只要我们不去解引用这个指针就不会出错。实际意思就是我们强行把0这个地址用TYPE类型的结构体类型解释了一番。管他存不存在,至于为什么选用0地址?是为了更方便的求取其他成员变量的偏移地址。
(2)((TYPE *)0)->MEMBER ----------------> ((struct student *)0)->a
  其实就是通过结构体指针指向成员变量
(3)&((TYPE *)0)->MEMBER ----------------> &(((TYPE *)0)->MEMBER) ----------------> &(((struct student *)0)->a)
  这句的解释应该是先获得结构体变量,再对这个变量取地址。自然获取的就是这个结构体变量的实际地址,而改结构体被强行安放在0地址处,因此获得的成员变量的地址就是该变量的便宜地址。最后使用int强制类型转换一下。

三.container_of宏

<1>container_of宏的作用是:根据结构体成员变量地址推出结构体变量首地址。
<2>offset宏原理:一句话就是当前变量的地址减去该变量的偏移量(可以使用上面的offsetof宏计算)就是该结构体变量的首地址。
<3>offset宏表达式:

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

1.示例代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//<1>offsetof宏的使用
//offset宏的作用是:用宏来计算结构体中某个元素和结构体首地址的偏移量(其实质就是通过编译器来帮我们计算)。
//其中:TYPE表示结构体名,MEMBER表示成员变量名
#define offsetof(TYPE,MEMBER) ((int)&((TYPE *)0)->MEMBER)

//<2>container_of宏的使用
//使用container_of宏根据结构体成员变量的地址,获取结构体变量的地址
//其中:ptr是指向结构体元素member的指针,type是结构体类型,member是结构体中一个元素的元素名
//这个宏返回的就是指向整个结构体变量的指针,类型是(type *)
#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})
		
struct student
{
	char a;      //8(4+4)
	double b;    //8
	int c;       //8(4+4)
};

int main(int argc,char *argv[])
{
	struct student stu1 = {0};     //定义结构体变量stu1
	struct student *getstu;        //用来接收获得的结构体变量地址
	getstu = container_of(&stu1.b,struct student,b);   //根据成员变量b的地址获取结构体变量地址并返回
	printf("getstu = %p\n",getstu);   	//打印获取的地址
	printf("&stu1 = %p\n",&stu1);   	//打印实际的地址
	return 0;
}

2.结果展示

  计算出结构体变量的首地址于原实际地址相比较,可以看出计算得出的地址完全正确!

getstu = 0x7ffce5ec4270
&stu1 = 0x7ffce5ec4270

3.container_of宏实现原理分析

  可以看出container_of宏一共就分为两条指向语句,其中第一句:const typeof( ((type *)0)->member ) *__mptr = (ptr); 的作用是:首先使用typeof获取该成员变量的类型,然后使用该类型定义一个指针变量__mptr并将该成员变量的地址ptr赋给__mptr。这样__mptr中保存的就是ptr的值。
  第二句是:(type *)( (char *)__mptr - offsetof(type,member) );}) 其实就是使用上面offsetof宏计算出该成员变量相对于该结构体变量首地址的偏移地址,然后当前地址-偏移地址=结构体变量首地址。就是很简单的数据计算,略有难度的是对C语言内存操作及变量类型转换的理解。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tutu-hu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值