总结
- 内存对齐的两要素:数据起始地址要等于内存边界的倍数、数据所占字节数要是对齐边界的倍数
- 32位系统中最大对齐边界是4个字节,64位系统是8个字节、
- 这里的最大对齐边界,和机器每次能操作的字节数相等,也就是机器字长
- go语言中,取类型大小和最大对齐边界的最小值,作为类型的对齐边界
- 为什么要内存对齐,为了提高性能,降低内存的浪费
内存
cpu在内存中读取数据的步骤
- 将内存地址通过地址总线传输给内存
- 内存根据地址准备好数据
- 内存将数据通过数据总线传输给cpu

如果地址总线只有八根,那这个地址只有八位,可以表示256个地址,所以256byte就是八根地址总线最大的寻址空间,要想使用更大的内存,就需要更大的地址总线,32位地址总线(0,2^32-1),可以选址4G内存
需要操作4字节,就最少需要32位数据总线,8字节就需要64位,这里每次操作的字节数就是所谓的机器字长
**
内存介绍
如果内存就像我们逻辑上认为的那样,一个挨一个行成一个大矩阵,我们可以访问任意地址,并把它输出到总线
但是为了更高的访问效率,最典型的内存布局如下
一个内存条的一面是一个rank,包含了多个chip,一个chip包含了8个bank,到了bank就可以通过选择行选择列来定位一个地址了

这样的结构不是我们逻辑上认为的那样,但是8个bank公用同一个地址,获取数据时,各自选择同一个位置的一个字节,组合起来作为我们逻辑上认为的连续的八个字节,通过这样的并行操作提高了内存的访问效率
但是使用这样的设计,那么address这里的地址就只能是8的倍数,如果非要错开一个字节,那么最后最后一个地址的位置和前面七个不同,不能再一次操作中被同一个地址选中,所以这样的地址是不能用的(硬件不支持),如下图
有一些cpu支持访问任意地址,是因为它多做了很多处理,如下:
如果我们取8字节的数据,从地址0开始去,那么cpu将会分两次获取,第一次获取前八个字节,只留后7个字节,第二次取后八个字节,只留第一个字节,组成我们需要的数据
内存对齐
概念
上面那样操作必然会影响到性能,所以为了保证程序高效的运行,编译器会把各种类型的数据安排的合适的地址,并占用合适的长度,这就是内存对齐
对齐边界
每种类型的对齐值就是他的对齐边界,内存对象要求数据的地址和占用字节数都要死它对齐边界的倍数,所以下面的int32要从4开始,却不能紧接着从2开始
如何确定对齐边界
对齐边界和平台也有关系,go语言支持下面的平台,在32位平台上,指针宽度和寄存器宽度都是4字节,64位机器上都是8字节,而被go语言成为寄存器宽度的这个值,就是机器字长,也是平台对应的最大对齐边界
数据类型的对齐边界,取类型大小和平台最大内存边界的较小的那个,需要注意相同的类型不同平台的类型大小不同,所以对齐边界可能也不同
确定结构体的对齐边界
首先要确定每个成员的对齐边界,然后其中最大的作为结构体的对齐边界
例:
试试存储下面这个结构体
type T struct {a int8b int64c int32d int16}
首先内存对齐的第一个要点,起始地址是要是对齐边界的倍数
结构体存储时,每个成员都要将结构体的起始地址当做地址0,再根据相对地址确定自己该放在什么位置
数据全部存好后并没有结束,要判断结构体占用的字节数是否是对齐边界的倍数,如果不是需要往后扩张一下,下图中结构体占用了22个字节,并不是8的倍数,所以需要往后扩张一下,所以扩张到相对地址23,最终这个结构体类型的大小就是24个字节
为什么要规定,类型的大小要等于对齐边界的整数倍?
为了保证内存中每一个数据都内存对齐。
如果不做扩张的话,那么上面那个结构体的类型大小就是22,假设这是一个长度为2的T类型的数组,那么这个数组会占用44字节的内存,第一个元素是0-21第二个元素是22-44,那么这个时候第二个元素并没有内存对齐,如下图:
如何优化这个结构体,降低内存占用呢?
type T struct {a int8d int16c int32b int64}
结构体字段的顺序换成这样,只会占用16个字节
