前言

第一次看到内存对齐时,俺是一头雾水,什么是内存对齐?为啥要这样做呢?带着这些疑问,俺查阅了相关资料,整理出本文。

首先,我们看一下T1结构体,猜一下会占用多少字节内存?

PS:本文如无特殊说明,均是指64位机器。

type T1 struct {
	a int8
	b int16
	c int64
}

有些童鞋不假思索的就得出了答案:1+2+8 = 11

很遗憾,答案是错误的,正确应该是 1+1+2+4+8 = 16

为什么会这样呢?

要解释这个问题,我们要先理解CPU对内存的读取机制。

CPU 读取内存方式

CPU 总是按字长(word size)读取内存。64位CPU字长为8字节,32位CPU字长为4字节。

T1 在内存对齐下的存储

memory layout1

因为内存对齐的,CPU 读取a或b时,一次读取即可,c 占完整字长也是一次读取。

T1 在内存未对齐下的存储 memory layout2

因为CPU总是按字长读取,而c分配在两个字长里,需要读2次。

Go 中内存对齐保证

理解了CPU 读取内存方式,我们看下Go的内存保证规则。

一个合格的Go编译器必须保证:

  1. 对于任何类型的变量x , unsafe.Alignof(x) 的结果最小为1 。
  2. 对于一个结构体类型的变量x , unsafe.Alignof(x) 的结果为x 的所有字段的对齐保证unsafe.Alignof(x.f) 中的最大 值(但是最小为1 )。
  3. 对于一个数组类型的变量x , unsafe.Alignof(x) 的结果和此数组的元素类型的一个变量的对齐保证相等。

T1的对齐保证长度

fmt.Println("t1 alignOf:", unsafe.Alignof(T1{})) // t1 alignOf: 8

那字段的顺序会影响内存占用大小么? 我们看下T2,确实会影响,如果对内存的分配有比较高的性能要求,可以适当进行优化。不过一般情况下不用考虑。

type T2 struct {
	a int8

	// 在64位架构上,为了让字段b的地址为8字节对齐,
	// 需在这里填充7个字节。在32位架构上,为了让
	// 字段b的地址为4字节对齐,需在这里填充3个字节。
	c int64
	// 为了让类型T2的尺寸为T1的对齐保证的倍数,
	// 在64位架构上需在这里填充6个字节,在32架构
	// 上需在这里填充2个字节。

	b int16
}

// 类型T2的尺寸在64位架构上为24个字节(1+7+8+2+6),
// 在32位架构上为16个字节(1+3+8+2+2)。

总结

内存对齐其实是空间换时间的典型应用。理解Go中内存分配规则,方便我们对内存进行估算。

Reference