什么是内存对齐

为了能让CPU可以更快的存取到各个字段,Go编译器会帮你把struct结构体做数据的对齐。所谓的数据对齐,是指内存地址是所存储数据的大小(按字节为单位)的整数倍,以便CU可以一次将该数据从内存中读取出来。编译器通过在结构体的各个字段之间填充一些空白已达到对齐的目的。

对齐系数

不同硬件平台占用的大小和对齐值都可能是不一样的,每个特定平台上的编译器都有自己的默认"对齐系数",32位系统对齐系数是4,64位系统对齐系数是8

Gounsafe.Alignof2^n
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    fmt.Printf("bool alignof is %d\n", unsafe.Alignof(bool(true)))
    fmt.Printf("string alignof is %d\n", unsafe.Alignof(string("a")))
    fmt.Printf("int alignof is %d\n", unsafe.Alignof(int(0)))
    fmt.Printf("float alignof is %d\n", unsafe.Alignof(float64(0)))
    fmt.Printf("int32 alignof is %d\n", unsafe.Alignof(int32(0)))
    fmt.Printf("float32 alignof is %d\n", unsafe.Alignof(float32(0)))
}

可以查看到各种类型在Windows 64位上的对齐系数如下:

bool alignof is 1
string alignof is 8 
int alignof is 8    
float alignof is 8  
int32 alignof is 4  
float32 alignof is 4

缺点

存在内存空间的浪费,实际上是空间换时间。

优点

CPUCPU
结构体对齐原则

对齐原则:

  1. 第一个成员在结构体变量偏移处为0的地址处
  2. 其他成员要对齐到某个数字(对齐数)的整数倍的地址处。对齐数=编译器默认的一个对齐数与该成员大小的较小值。每个特定平台上的编译器都有自己的默认"对齐系数",32位系统对齐系数是4,64位系统对齐系数是8。
  3. 结构体总大小为最大对齐数的整数倍
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体整体就是所有最大对齐数的整数倍。
type T1 struct {
    i16  int16 // 2 byte
    bool bool  // 1 byte
}

type T2 struct {
    i8  int8  // 1 byte
    i64 int64 // 8 byte
    i32 int32 // 4 byte
}

type T3 struct {
    i8  int8  // 1 byte
    i32 int32 // 4 byte
    i64 int64 // 8 byte
}

func main() {
    fmt.Println(runtime.GOARCH) // amd64

    t1 := T1{}
    fmt.Println(unsafe.Sizeof(t1)) // 4 bytes

    t2 := T2{}
    fmt.Println(unsafe.Sizeof(t2)) // 24 bytes

    t3 := T3{}
    fmt.Println(unsafe.Sizeof(t3)) // 16 bytes
}

以T1结构体为例子:

i16并没有直接放在bool的后面,而是在bool中填充了一个空白后,放到了偏移量为2的位置上。如果i16从偏移量为1的位置开始占用2个字节,根据对齐原则2:构体变量中成员的偏移量必须是成员大小的整数倍,套用公式 1 % 2 = 1,就不满足对齐的要求,所以i16从偏移量为2的位置开始

img

以T2结构体为例子:

img

以T3结构体为例子:

img