Golang的内存对齐

什么是内存对齐

CPU在访问内存的时候,并不是一个字节一个字节读取,而是以字长(word size)为单位访问。32位CPU字长为4字节,64位CPU字长是8字节。32CPU访问内存的时候只能以4的倍数的内存开始地址读取。如果一个变量存储在跨字长的内存区域上,CPU就需要多次访问内存才能读取。

Golang为了尽量避免这种跨字长的内存访问,在内存分配时会做一次内存对齐。

内存对齐示例

1
2
3
4
5
6
7
8
9
10
11
12
13
type Foo struct {
a bool
b uint32
c uint32
}

func main() {
f := Foo{}
fmt.Printf("Foo.a size: %d\n", unsafe.Sizeof(f.a))
fmt.Printf("Foo.b size: %d\n", unsafe.Sizeof(f.b))
fmt.Printf("Foo.c size: %d\n", unsafe.Sizeof(f.c))
fmt.Printf("Foo size: %d\n", unsafe.Sizeof(f))
}

输出结果

1
2
3
4
Foo.a size: 1
Foo.b size: 4
Foo.c size: 4
Foo size: 12

理论上Foo有三个字段,各个长度相加应该是1 + 4 + 4 = 9通过输出我们看到它实际占用的空间是12,这就是内存对齐的结果

内存对齐规则

  1. 每个平台的编译器都有一个默认的对齐系数,对齐系数可以通过预编译命令#pragma pack(n)来修改
  2. 结构体的成员变量对齐值等于成员变量大小和对齐系数较小的数 value = min(size, n)
  3. 结构体第一个成员变量偏移量是0, 后面的成员变量偏移量必须是成员变量对齐值的整数倍
  4. 结构体本身的对齐值是成员变量对齐值的最大值

我们再看一下上面的对齐示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

type Foo struct {
a bool
b uint32
c uint32
}

func main() {
f := Foo{}
fmt.Printf("Foo.a size: %d, align: %d, offset: %d\n", unsafe.Sizeof(f.a), unsafe.Alignof(f.a), unsafe.Offsetof(f.a))
fmt.Printf("Foo.b size: %d, align: %d, offset: %d\n", unsafe.Sizeof(f.b), unsafe.Alignof(f.b), unsafe.Offsetof(f.b))
fmt.Printf("Foo.c size: %d, align: %d, offset: %d\n", unsafe.Sizeof(f.c), unsafe.Alignof(f.c), unsafe.Offsetof(f.c))
fmt.Printf("Foo size: %d, align: %d\n", unsafe.Sizeof(f), unsafe.Alignof(f))
}

// 输出
Foo.a size: 1, align: 1, offset: 0
Foo.b size: 4, align: 4, offset: 4
Foo.c size: 4, align: 4, offset: 8
Foo size: 12, align: 4

空结构体对齐

在Golang中空结构体struct{}大小为0,不占用内存空间,理论上他不需要对齐,但是当空结构体是最后一个成员变量时也需要内存对齐,因为如果有指针指向该成员变量的话,空结构体也需要一个地址,CPU访问这个地址的时候也需要按字来访问

验证代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Foo struct {
a uint64
b struct{}
}

func main() {
f := Foo{}
fmt.Printf("Foo.a size: %d, align: %d\n", unsafe.Sizeof(f.a), unsafe.Alignof(f.a))
fmt.Printf("Foo.b size: %d, align: %d\n", unsafe.Sizeof(f.b), unsafe.Alignof(f.b))
fmt.Printf("Foo size: %d\n", unsafe.Sizeof(f))
}

Foo.a size: 8, align: 8
Foo.b size: 0, align: 1
Foo size: 16