当前位置:网站首页>offsetof宏与container_of宏分析详解
offsetof宏与container_of宏分析详解
2022-07-28 10:32:00 【tutu-hu】
一.概述
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语言内存操作及变量类型转换的理解。
边栏推荐
猜你喜欢
随机推荐
网络文件系统服务(NFS)
Samba learning
Select without the order by clause, the order of the returned results is not reliable
Header library file
Blue Bridge Cup embedded Hal library LCD
Redis-day01 common sense supplement and redis introduction
GKRandom
GKPolygonObstacle
Judge whether the nixie tube is a common anode or a common cathode
Andorid 开发三 (Intent)
float浮动初步理解
nodemcu之开发环境配置
Blue Bridge Cup embedded Hal library USART_ TX
读懂这6本书,学习MySQL更轻松
Development environment configuration of nodemcu
GKVoronoiNoiseSource
学会这些分析方法及模型,遇到问题不再没思路
02.1.2. logic type bool
Andorid development III (intent)
国内外优秀程序员的博客全在这了,请查收









