内存对齐原则

1,921 阅读5分钟

内存对齐原则

灵魂拷问之为什么需要内存对齐规则,难道按照内存依次排序不好嘛?

内存对齐的原因

1.平台原因(移植原因): 一些资料上是这样说的,“不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些特定地址处取某些特定的数据,否则就会抛出硬件异常”。也就是说在计算机在内存读取数据时,只能在规定的地址处读数据,而不是内存中任意地址都是可以读取的。

2.效率原因: 正是由于只能在特定的地址处读取数据,所以在访问一些数据时,对于访问未对齐的内存,处理器需要进行两次访问;而对于对齐的内存,只需要访问一次就可以。 其实这是一种以空间换时间的做法,但这种做法是值得的。

结构体内存对齐原则

1.第一个成员在结构体变量偏移量为0 的地址处,也就是第一个成员必须从头开始。

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 为编译器默认的一个对齐数与该成员大小中的较小值。mac 64位系统下默认应该是8字节(当然可以通过#pragma pack()修改),但修改只能设置成1,2,4,8,16.

3.结构体总大小为最大对齐数的整数倍。(每个成员变量都有自己的对齐数)

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

========================================================

看到这里,一个字晕啊,两个字好晕啊,什么最大对齐数,什么对齐数的整数倍,该怎么理解这些原则呢,来,对比上面各种数据类型所占内存空间大小的图, 看看下面的实例,

例1

结果打印出来是8,试着用上面的原则分析下如果计算结构体s1的内存大小,根据上面原则一,char c1 在结构体地址偏移量为0也就是最开始处,1个字节,char c2 占1个字节 参考对齐原则2,需要对齐到对齐数整数倍的位置,对齐数=编译器默认的对齐数与该成员大小种比较小的那一个,在不更改编译器默认对齐数的情况下,1字节小于编译器默认对齐数8字节,那么char c2在地址偏移量为1的地址空间开始,int i 占4个字节,按照对齐原则2,需要对齐到对齐数4的整数倍,也就说int i的内存地址是在地址偏移量为4的地址空间开始,所以最后结果是8.

我们来看下内存分布图

例2:

带着刚刚例1分析完的思路我们再来分析例2,char c1 占1个字节,在偏移量为0的开始处,int i 占4个字节,需要对齐到4的整数倍,在偏移量为4的地址空间处开始,char c2 占一个字节,需要对齐到1的整数倍,这时候 4 + 4 + 1 = 9,此时参考内存对齐原则3,结构体总大小为最大对齐数的整数倍,struct s2结构体内最大对齐数就是int i变量的对齐数4,也就是说s2的内存大小必须是4的倍数,我们已经使用了9个字节的空间,那么最后s2的内存大小为12,这是结合内存对齐原则1、2、3的使用。

我们来看下内存分布图

例3:

首先我们先计算结构体s3的内存大小,double d 占8个字节,在偏移量为0的开始处,char c 占1个字节,根据对齐原则2,对齐到1的整数倍,也就是从偏移量为8的地址空间开始,int i 占4个字节,对齐到4的整数倍,也就是偏移量为12的地址空间开始,那么s3的结构体大小就为16,符合对齐原则3,结构体总大小为最大对齐数的整数倍。继续分析结构体4,char c1 占1个字节,在地址空间开始处,struct3 占16个字节,根据对齐数的定义(对齐数=编译器默认的对齐数与该成员大小种比较小的那一个),s3对齐到编译器默认对齐数8的整数倍,也就是从偏移量为8的地址空间处开始,double d 占8个字节,对齐到8的整数倍,也就是从地址空间偏移量为24的地方开始,最后计算得出 s4的结构体内存大小为32,对于对齐原则4的解释,我的理解是,结构体s3对齐到自己最大对齐数的整数倍处,那么s4结构的的大小是char c1, struct s3 double d,所有最大对齐数的整数倍。

我们来看下内存分布图

然后最后做个有趣的小实验,仍然是s3和s4的结构体,假如我通过#pragma pack(4) 改变编译器默认的对齐数,那么此时s4的内存大小是多少呢?

================================

按照例3的思路解答, 结果是28。

================================

PS:欢迎留言探讨交流,目的纯粹为了学习进步。

参考文献:关于结构体中的内存对齐问题