Go结构体内存对齐
先来看一个示例
package main
import (
"fmt"
"unsafe"
)
type struct1 struct {
a bool
b int64
c bool
}
type struct2 struct {
x bool
y bool
z int64
}
func main() {
fmt.Printf("struct1 大小 %d 对齐系数 %d\n", unsafe.Sizeof(struct1{}), unsafe.Alignof(struct1{}))
fmt.Printf("struct1.a 大小 %d 对齐系数 %d 偏移量 %d\n", unsafe.Sizeof(struct1{}.a), unsafe.Alignof(struct1{}.a), unsafe.Offsetof(struct1{}.a))
fmt.Printf("struct1.b 大小 %d 对齐系数 %d 偏移量 %d\n", unsafe.Sizeof(struct1{}.b), unsafe.Alignof(struct1{}.b), unsafe.Offsetof(struct1{}.b))
fmt.Printf("struct1.c 大小 %d 对齐系数 %d 偏移量 %d\n", unsafe.Sizeof(struct1{}.c), unsafe.Alignof(struct1{}.c), unsafe.Offsetof(struct1{}.c))
fmt.Printf("\nstruct2 大小 %d 对齐系数 %d\n", unsafe.Sizeof(struct2{}), unsafe.Alignof(struct2{}))
fmt.Printf("struct2.x 大小 %d 对齐系数 %d 偏移量 %d\n", unsafe.Sizeof(struct2{}.x), unsafe.Alignof(struct2{}.x), unsafe.Offsetof(struct2{}.x))
fmt.Printf("struct2.y 大小 %d 对齐系数 %d 偏移量 %d\n", unsafe.Sizeof(struct2{}.y), unsafe.Alignof(struct2{}.y), unsafe.Offsetof(struct2{}.y))
fmt.Printf("struct2.z 大小 %d 对齐系数 %d 偏移量 %d\n", unsafe.Sizeof(struct2{}.z), unsafe.Alignof(struct2{}.z), unsafe.Offsetof(struct2{}.z))
}
输出
struct1 大小 24 对齐系数 8
struct1.a 大小 1 对齐系数 1 偏移量 0
struct1.b 大小 8 对齐系数 8 偏移量 8
struct1.c 大小 1 对齐系数 1 偏移量 16
struct2 大小 16 对齐系数 8
struct2.x 大小 1 对齐系数 1 偏移量 0
struct2.y 大小 1 对齐系数 1 偏移量 1
struct2.z 大小 8 对齐系数 8 偏移量 8
内存布局示例图:
先说下为什么要内存对齐
许多计算机系统对基本数据类型的合法地址做出了一些限制,要求某种类型对象的地址必须是某个值 K(通常是 2、4 或 8)的倍数。这种对齐限制简化了形成处理器和内存系统之间接口的硬件设计。例如,假设一个处理器总是从内存中取 8 个字节,则地址必须为 8 的倍数。如果我们能保证将所有的 int64 类型数据的地址对齐成 8 的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个 8 字节内存块中。
无论数据是否对齐,X86-64 硬件都能正确工作。不过,Intel 还是建议要对齐数据以提高内存系统的性能。
上面这段来自于 csapp 3.9.3 章节 ,通俗点说就是 cpu 读取一般是读取固定位数,CPU访问非对齐的内存时为何需要多次读取。
go 的内存对齐
For the numeric types, the following sizes are guaranteed:
type size in bytes
byte, uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64, float64, complex64 8
complex128 16
The following minimal alignment properties are guaranteed:
For a variable x of any type: unsafe.Alignof(x) is at least 1.
For a variable x of struct type: unsafe.Alignof(x) is the largest of all the values unsafe.Alignof(x.f) for each field f of x, but at least 1.
For a variable x of array type: unsafe.Alignof(x) is the same as the alignment of a variable of the array’s element type.
A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory.
对于数值类型,保证以下大小:
类型 大小(字节)
byte, uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64, float64, complex64 8
complex128 16
保证以下最小对齐属性:
对于任何类型的变量 x:unsafe.Alignof(x) 至少为 1。
对于结构体类型的变量 x:unsafe.Alignof(x) 是 x 中每个字段 f 的 unsafe.Alignof(x.f) 的最大值,但至少为 1。
对于数组类型的变量 x:unsafe.Alignof(x) 与数组元素类型的变量的对齐方式相同。
如果一个结构体或数组类型不包含大小大于零的字段(或元素),则其大小为零。两个不同的零大小变量在内存中可能有相同的地址。
为什么上面的结构体布局是这样
对于 struct1 来说 b 的对齐系数是 8 所以不能仅跟着 a 而是需要位于第 8 字节处 然后 c 位于第17字节处 struct1 的整体对齐系数是 8 所以需要在 struct1 后面填充 7 字节 使其满足 struct1 的对齐系数。
这里不知道大家会不会有疑问,反正本人是有,为什么 struct1 的后面要填充 7 个字节 。答案很简单 看下面这个操作你就明白了
var arr []struct1
如果 struct1 的后面不填充 7 个字节则无法保证 arr 里面结构体的内存对齐。 所以要要求结构体的大小是其偏移量的倍数。
疑问的解决来自于 stackoverflow
知道这个有什么作用
就像实例中看见的一样 同样是两个bool 一个 int64 字段的结构体,占用的内存大小却不一样,所以如果了解这个,写代码时你就可以节省内存。
也有一些开源工具是做这个的。
如structslop 但是我记得我还见过别的 一时没找见。
一个注意点。
当 struct{} 作为结构体最后一个字段时,需要内存对齐。因为如果有指针指向该字段, 返回的地址将在结构体之外,如果此指针一直存活不释放对应的内存,就会有内存泄露的问题(该内存不因结构体释放而释放)。
参考
本作品采用《CC 协议》,转载必须注明作者和本文链接