哈喽,大家好啊,我是情谊,今天我们来讨论一下结构体中内存对齐的问题,好嘞,话不多说,我们直接开始。
注意!!!我们以下的讨论是建立在已经创建了结构体变量的情况下,未创建变量是不会存储数据的!!!
在开始之前,我们先来思考一下,同一个内容的结构体,改变它的内部成员顺序,他的内存存储大小会改变吗?带着这个问题,我们看下面两个结构体
在上面的结构体中,我们知道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了。
好啦,以上便是我们今天讨论的全部内容啦,如果大有什么意见,欢迎在评论区指出来哦,然后还是希望看到这里的铁铁们能否点一个小赞呢?你们的认可就是我继续努力的动力,谢谢大家啦!我们下周再见!