关于结构体,我们可以深入探讨一个话题,计算结构体的大小。
当然我们计算结构体的大小就需要知道结构体的内存对齐.
首先,我们可以先来看一个例子
这里有两个结构体类型,他们的成员都是两个 char 一个int,那么它们的大小是多少呢?一个char 一个字节,一个int 四个字节,那么两个char 一个int就是6个字节,那么结果输入真的是这样吗?
很显然结果并不是6个字节,那么结构体在内存中是怎么存放的呢?
这就涉及到了结构体在内存中的内存对齐
首先我们需要了解内存对齐的规则:
1.结构体第一个成员变量对齐结构体变量的起始位置偏移量为0的地址处。
内存会为结构体创建一块空间
假设这就是内存为结构体创建的空间,蓝线就是结构体变量的起始位置,每个方格代表1个字节,偏移量就是距离起始位置相差多少,第一块格子距离起始位置相差为0,所以偏移量是0
第一个成员变量对齐,一定是从偏移量为0的地址开始,假如是一个字节,就在这里放一个字节,假如是两个字节,就按照顺序从开始往下放两个字节以此类推。
2.其他成员变量则是要对齐,对齐数的整数倍(编译器默认的对齐数与该成员变量的大小取较小值)(VS的默认对齐数是8,Linux中gcc没有默认对齐数)
比如 在 struct S结构类型中,第二个结构体变量是int 类型,那么它的大小是 4 个字节 如果在VS中运行,那么VS默认的对齐数是 8, int 大小4 个字节,与默认对齐数8相比,取较小值,来作为它真正的对齐数。那么此时就取4来作为对齐数,因为其他成员变量要对齐,对齐数的整数倍,所以 第二个变量 要对齐 4的整数倍,所以这个时候 在第二个结构体变量 存放时,会从偏移量为4的倍数开始存放,此时偏移量为4的空间没有东西,那么int 类型会直接从4开始,往下存放4个字节。
最后一个 变量char c2,它的大小 是1 个字节,VS默认对齐数是8,那么 1 和8 取较小值作为真正对齐数, 所以选择1,那么char c2变量的存放需要对齐 1的倍数,我们就只需要把 char c2变量放在 1 的倍数的 偏移量处,只需要把char c2放在 int 变量 存放过的地址往下一个的地址就好了
此时总大小已经来到9个字节,可是还没有结束
3.结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的
整数倍,我们知道,char c1 的对齐数是 1,int a的对齐数是 4,char c2的对齐数是1,那么结构体的总大小,必须是这些变量最大对齐数的整数倍,而这里最大的对齐数是 4,而目前9个字节的总大小并不是4的的整数倍,所以需要继续往下三个字节,让总大小变成12个字节,这样才是最终结构体的总大小,而没有使用到的字节空间则是被直接浪费掉了。
然后我们可以依照刚刚的办法,来计算 结构体变量 struct T
我们算出 8 就是全部字节,那么是 总大小吗? 我们再来看看 成员中的最大对齐数,是 int a 4,
8是 4的倍数,那么8就是 struct T的总大小。 没有使用的空间则是被直接浪费了。
4.以下情况,
如果嵌套了结构体的情况,嵌套的结构体成员 struct T对齐的是自己的结构体成员中最⼤对⻬数的整数倍处,我们可以从上面就可以知道 struct T 自己的结构体成员中最大的结构体变量的对齐数是 4,所以struct T只需要 对齐 4,在4的倍数的偏移量开始存放空间就可以了。而struct 我们已经算出了它的总大小是 8个字节,所以我们只需要在4的偏移量处以此存放8个字节。结构体的整体大小依然是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍,short的 对齐数是 2, Struct T的对齐数是 4,int i 的对齐数 是4,所以这里最大的对齐数是4,我们只需要比较我们计算的结构体大小,是否是结构体变量中最大对齐数4的倍数,如果是那么这就是最终大小,如果不是,那么需要往下补空间,补到最终字节大小是 对齐数的倍数就可以了,补的空间没有使用被直接浪费
所以我们可以算出,Struct D的结构体大小是 16个字节。我们可以通过代码结果来验证我们的思路。