3.6 内存对齐、内存布局是怎么回事? ================================== 字长(word size) ----------------- 字长(word size),指的是 CPU 一次可以访问数据的最大长度: - 对于 32 位的 cpu 来说:word size 为 ``2^32``\ ,即 4 byte - 对于 32 位的 cpu 来说:word size 为 ``2^64``\ ,即 8 byte 两种内存布局 ------------ 下边是一个结构体的两种内存布局方式 第一种:按顺序 ~~~~~~~~~~~~~~ 代码 .. code:: go type Foo struct { A int8 // 1 B int8 // 1 C int8 // 1 } type Bar struct { x int32 // 4 y *Foo // 8 z bool // 1 } 你觉得 Bar 对象会占用多少的内存? 可能很多人会下意识地回答 **13** 会回答 13 是因为你觉得该结构体的内存分配是下面这样按顺利分配的。 按照前面所介绍的 word size 为 8 来计算,使用这种分配方式,当你访问 bar.y 的时候,CPU 需要访问内存两次。 .. figure:: http://image.iswbm.com/20210925153036.png :alt: memory layout of Bar1 memory layout of Bar1 第二种:按字长 ~~~~~~~~~~~~~~ 而如果使用下面这种方式,当你再次访问 bar.y 的时候,CPU 需要访问内存一次。 .. figure:: http://image.iswbm.com/20210925153041.png :alt: memory layout of Bar1 memory layout of Bar1 因此真正的答案是 **24**\ ,这是一种典型的用空间换时间的方法 – **内存对齐** .. code:: go func main() { var bar Bar fmt.Println(unsafe.Sizeof(bar)) // 24 } 合理定义结构体 -------------- 从以上可以发现,我们定义的结构体虽然不大,只占用 24个byte,但实际有用的只有 13 的byte,内存使用率只有 50% 左右,很有优化的必要性。 如果将第三个属性挪 ``y`` 的前面 .. figure:: http://image.iswbm.com/20210925154455.png :alt: memory layout of Bar2 memory layout of Bar2 那就可以省下来 1 个 byte 了 .. code:: go func main() { var bar Bar fmt.Println(unsafe.Sizeof(bar)) // 16 } y 为什么占用 8 字节? --------------------- 看完了上面的介绍,想必你一定有一个疑问: Foo 结构体实际占用 3个byte,为什么 Bar.y 却要占用 8个 byte 呢? .. code:: go type Foo struct { A int8 // 1 B int8 // 1 C int8 // 1 } type Bar struct { x int32 // 4 y *Foo // 8 z bool // 1 } 因为 ``Bar.y`` 表示的是一个指针,而指针的对齐系数是 8 .. code:: go func main() { var bar Bar fmt.Println(unsafe.Alignof(bar.y)) // 8 } 你大可将 y 改成普通对象 .. code:: go type Foo struct { A int8 // 1 B int8 // 1 C int8 // 1 } type Bar struct { x int32 // 4 y Foo // 3 z bool // 1 } 这样一来,bar 对象就只占用一个字长 .. code:: go func main() { var bar Bar fmt.Println(unsafe.Sizeof(bar)) // 8 }