前言:
大家在学习结构体中,在计算结构体大小时想必会很疑惑,为什么结构体的大小不是按照常理像数组一样一个字节一个字节的挨在一起放?今天带大家一起深入探讨一下背后的规则和原因。
结构体对齐规则:
结构体对齐其实就是所有成员变量都要对齐到对齐数整数倍的地址处
首先认识一下默认对齐数的概念,每个编译器都有默认对齐数,我这里使用的是vs2022,它的默认对齐数是8。
对齐数的计算规则是:
对齐数=编译器默认对齐数与改成员变量大小(字节)的较小值
还有一个重要特征:
结构体总大小为最大对齐数的整数倍。
接下来我举两个实例:
我定义一个如下结构体:
接下来开始计算每个成员变量的对齐数。
第一个int a,自身大小为4,根据上面的对齐数计算公式,所以它的对齐数就是4
第二个char b,自身大小为1,同理,对齐数是1
因为第一个变量对齐数是4,所以它要对齐到4的整数倍,刚好0-3地址可以存放它
第二个对齐数是1,任何位置都是1的整数倍,所以直接接在a的后面1个字节,但最后结构体的大小可不是5
因为要满足结构体总大小是最大对齐数的整数倍,此时最大对齐数是4,5不是4的整数倍,8才是4的整数倍,所以结构体总大小应该为8,这里浪费了3个字节的空间,此时在内存中的安排应该如下图:
再来一个例子把a,和b的顺序调换一下:
对齐数跟刚才上面一样,我不再计算注释在图中了。
第一个变量跟刚才一样,任何位置都是1的整数倍,所以直接放在0号位
第二个变量对齐数为4,不能直接接在第一个变量后面,因为要对齐到对齐数整数倍位置,所以要从第四个字节开始放,如下图:
、
所以这个结构体的大小也是8,从上面两个例子不难看出,虽然结构体大小一样但它们的内存安排是可以不一样的。
注意如果结构体里面嵌套了结构体,那么嵌套在里面的结构体对齐数是:
该结构体的最大对齐数。
如下图所示:
为什么要结构体对齐?
从上面的例子不难看出,结构体对齐是会浪费空间的,可是为什么要这样做呢?其实就是以空间换时间,那它是怎么来换时间的呢?
首先需要明白一点,我们的cpu读取数据不是一个字节一个字节的读,假设我们是32位的机器(64位的同理),cpu上就会有32根总线,每根总线读取一个比特位,一个字节8个比特位,换算过来就是一次可以读取4个字节,所以这就导致了cpu一只只能固定读取4个字节,而且必须从4的整数倍处开始读取。
接下来给大家举个例子:
对比一下对齐和不对齐的内存安排,它们在内存里就是这样的:
刚才解释了我们的32位的cpu只能从4的整数倍开始读,假设我们要读a这个数据。
我们先来看看不对齐的时候该怎么读a:
首先它不能从1号位地址开始读,因为这里不是4的整数倍,cpu只能从0读到3或者从4读7,这就导致了不对齐的情况想读a的话,需要先读0-3,读取a的上面一部分,再读4-7读取a的下面一部分把这两部分组合起来,所以读了两次才能读到a。
再来看看对齐的情况:
对齐的话,我们就可以直接读4-7,一次即可把a读完。
总结:
所以,不难看出对齐可以大大减少我们cpu的压力,提高效率,以空间换时间。