详解结构体中内存对齐问题

6 篇文章 0 订阅
2 篇文章 0 订阅

哈喽,大家好啊,我是情谊,今天我们来讨论一下结构体中内存对齐的问题,好嘞,话不多说,我们直接开始。

注意!!!我们以下的讨论是建立在已经创建了结构体变量的情况下,未创建变量是不会存储数据的!!!

在开始之前,我们先来思考一下,同一个内容的结构体,改变它的内部成员顺序,他的内存存储大小会改变吗?带着这个问题,我们看下面两个结构体

在上面的结构体中,我们知道char类型只占1个字节,int占据了 4个字节,所以按直接数的情况,这两个结构体的内存大小都是6,但其实不是的,第一个结构体真实情况是占据了8个字节,第二个结构体是占据了12个字节,那为什么呢?我们接下来继续看。

我们直接先看规则:

1第一个成员在与结构体变量的偏移量为0的地址处

2其他的成员变量需要对齐到某个数字(对齐数)的整数倍的地址处

对齐数:编译器默认的对齐数和该成员类型大小的较小值。

(我们今天使用的编译器是Visual Studio2022,对齐数的默认值是8)

3结构体的总大小为最大对齐数(所以成员之间相比)的整数倍

4如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

对齐的规则看完了,我们来进行讲解。

第一个规则:第一个成员在与结构体变量的偏移量为0的地址处     

这句话表示结构体内存储存是连续的,从一个位置的地址依次向下存储的,即如同数组一般,首元素的下标为0,然后依次存储,即如图所示:

其中成员量的第一个类型存储就是从0开始存储的,例如我们上面举出的例子,第一存储的是char a

第二个规则: 其他的成员变量需要对齐到某个数字(对齐数)的整数倍的地址处

这个规则是在说第一个数据存储之后,其他的数据的存储都需要是对齐数的整数倍,我们依然看之前的两个代码,其中第一个代码中的第二个需要存储的数据是char b,由对齐数的定义我们可以知道,char类型的对齐数是1(char类型的大小是1,但vs编译器的标准对齐数是8,所以对齐数我们取最小值1),所以我们存储char类型时需要对齐到char类型的对齐数的整数倍,即是1的整数倍,接下来我们看内存中的下标编号是1,所以我们就可以存储b,如图所示:

存储完char  b之后,我们看下一个存储对象int  i  ,由规则二,我们需要对齐到int  i 对齐数的整数倍,int  i 的对齐数是4(int 类型的大小是4个字节,vs编译器标准对齐数是8),接下来我们看内存条上面,下一个空间的下标是2,不是int的对齐数4的整数倍,所以我们就要浪费空间保证对齐,一直浪费到下标为4的内存条上再进行存储,如图所示:

第三个规则: 结构体的总大小为最大对齐数(所以成员之间相比)的整数倍

在这个规则中,我们需要看结构体中所有成员的对齐数,并找最大的对齐数,我们这里找到对齐数是4,接下来我们再看存储的数据是不是对齐数的整数倍,我们这里是的,所以这个结构体的存储就完成了,占据了8个空间(注意!!,规则三看的是结构体的大小是最大对齐数的整数倍,而不是下标是对齐数的整数倍)

规则四我们最后来讨论,我们接下来讨论一下上面的第二个结构体内存创建的形式

首先,我们还是从下标0开始存储,存储了第一个成员char  a,如图:

  然后存储int  i ,他的对齐数依然是4,但是我们需要存储到内存条的整数倍上,所以下标为1,2,3的内存就被浪费掉了。

 接下来我们将char  b一样的方法存进内存中,得到如图所示:

最后我们看规则三,要求结构体的内存值为最大对齐数的整数倍,而成员中的最大对齐数是4,所以要求是4的倍数,因此还要浪费空间到12个,即浪费到下标11,如图:

所以,结构体二的内存我们也创建好了。

 接下来,我们看嵌套结构体

第四个规则:如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

我们以下面这个结构体三为例:

在上面的结构体中,s3中还有一个结构体s2,这就是结构体之间的嵌套,我们存储a和d的空间是遵守前面三条规则的,而struct  s2的存储就需要规则四,规则四中说明结构体的对齐数是其成员对齐数中的最大者,在上面的代码中,结构体s2的对齐数就是4,然后再看下一句,整个大的结构体s3就是内部成员中最大对齐数的整数倍,则s3的最大对齐数就是8 ,我们这里就不再进行过多的阐述啦。

内存对齐的好处

讲完了四条结构体对齐的规则,那我们也来说一下结构体对齐的原因吧

其实不是所有的平台都能访问任意位置的任意数据的,有一些平台上只能访问特定位置的数据,否则就抛出硬件异常。而且,如果不进行数据对齐,那么我们可能会浪费时间,增加访问次数,例如:

访问这个数据的内存时,若不进行对齐,在我们32位的机器下,一次性访问的是4个字节

 第一次访问四个字节,那么int  i的值就一次性访问不完,需要再增加一次,而对比对齐了的数据,int  i 的值一次性就可以访问完全,其实总的来说,对齐的情况就是在用空间换取时间!

修改标准对齐数 

其实vs编译器上默认的对齐数是8,但是我们也可以修改这个对齐数,这时候我们就需要用到预处理指令#pragma  pack() , 具体使用方法如下:

 

在结构体前面加上一句#pragma  pack(想要修改的对齐数),再在末尾加上#pragma  pack(),这里相当于一个开关,前一句就是开启,末尾就是修改结束,注意,修改的对齐数只在这两句话里里面实现,例子:

上面这个代码就是将编译器的对齐数修改为1了。

好啦,以上便是我们今天讨论的全部内容啦,如果大有什么意见,欢迎在评论区指出来哦,然后还是希望看到这里的铁铁们能否点一个小赞呢?你们的认可就是我继续努力的动力,谢谢大家啦!我们下周再见! 

 

  • 31
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值