结构体内存对齐与结构体类型的大小
运行这样一段代码
我们想要计算这两个结构体类型的大小,而这个结构体类型里面成员变量有一个int类型和两个char类型,大小加起来应该是六个字节,但是我们打印出来发现,结果居然是12和8,不仅不是6,甚至都两次结果都不一样大,这是为什么呢?
通过offsetof计算出struct s1各个成员变量离起始地址的偏移量分别是0,4,8,(offsetof是一个宏,具体用法这里就不展开讲了)于是我们可以画出struct s1的成员变量在内存中的分布
那也就是占了九个字节,为什么struct s1的大小会是12呢?
这就涉及到了内存对齐
首先得掌握结构体的对齐规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。VS中默认的值为8,gcc环境没有对齐数
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
知道了这些规则之后再来看上面的struct s1在内存中为什么这样存储,struct s1的第一个成员变量是char类型的,因此对齐数是1,因此第一个成员变量就放在了偏移量为零的单元,第二个成员变量i是int类型的,对齐数是4,应该放偏移量是在4的整数倍的单元处,因此从4开始放四个字节,浪费了三个字节单元,第三个成员变量也是char类型的,对齐数是1,因此接着放一个字节,如图所示
一共占用了9个字节,但是根据第三条规则,结构体类型的大小必须是所有成员变量中最大对齐数的整数倍,struct s1的成员变量最大对齐数是4,9不是4的倍数,此时离着4的倍数最近的字节数是12,因此struct s1的大小是12个字节。
同样的,再来看struct s2
他的第一个成员变量是int类型的,从偏移量为零的地址处开始存放,放了四个字节,第二个成员变量是char类型的,对齐数是1,直接接着放就行,因为任何数都是1的整数倍。第三个成员变量也是char类型的,对齐数是1,接着放一个字节,发现一共用了6个字节,最大对齐数是4,6不是4的倍数,离着最近的一个4的倍数是8,因此struct s2的大小是8
结构体嵌套问题
struct s4类型的第一个成员变量是char类型的,对齐数是1,直接在偏移量为0的地址处开始存放,第二个成员变量是struct s3类型的,他的成员变量最大对齐数是8,因此他要对齐到偏移量为8的整数倍的位置,离着最近的就是从偏移量为8的位置开始存放,存放多少个字节,那就要看struct s3类型占多少个字节。
我们假设又有一块空间存放着struct s3
struct s3的第一个成员变量是double类型,对齐数是8,从偏移量为0的地址处开始存放,存放了8个字节,存到了偏移量为7的位置,第二个成员变量是char类型的,对齐数是1,接着存一个字节,存到了偏移量为8的位置,第三个成员变量是int类型的,对齐数是4,但是接下来要存的单元偏移量是9,不是4的整数倍,因此要从偏移量为12的位置开始存放四个字节,到了偏移量为15的位置。又因为struct s3的成员变量最大对齐数是8,因此struct s3所占的字节大小就是离15最近的8的整数倍也就是16.
再回到struct s4的存储,从偏移量为8的单元开始放16个字节,到了偏移量为23的位置,struct s4的第三个成员变量是double类型的,对齐数是8,而接下来要存放的位置偏移量是24,恰好是8的整数倍,因此接着存放8个字节单元到了偏移量为31的单元处。共计占用了32个字节。所有成员变量的最大对齐数是8,而32恰好是8的整数倍,因此struct s4的大小是32个字节
为什么存在内存对齐?
1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说结构体的内存对齐是拿空间来换取时间的做法。