Go语言是如何进行内存对齐的?
type T struct {
a bool
b int8
c uint16
d uint32
e int64
f bool
}
类型尺寸
go白皮书只对以下类型占用的内存大小进行了明确规定.
uint和int类型的大小取决于编译器和系统架构,通常32位架构为4字节,64位架构为8字节
uintptr 取决于编译器实现,但必须保证一定能够存下任一个内存地址.
除了上述明确规定的,白皮书没有对其他种类的类型尺寸进行明确规定.
类型尺寸获取代码:
func main() {
var i bool = true
var j int = 40
fmt.Println("bool-size", unsafe.Sizeof(i))
fmt.Println("int-size", unsafe.Sizeof(j))
}
输出结果:
bool-size 1
int-size 8
bool类型的大小占用1字节
int类型的大小占用8字节,因为我是64位系统,如果你是32位的输出会是4
如果仅仅按照类型尺寸的规则,来累加计算结构体T中每个字段的存储大小,发现和实际存储的大小不一样的.因为Go在分配结构体内存的时候,还需要参照对齐保证。
对齐保证:
对齐保证也称为值地址对齐保证,也就是在分配变量存储位置的时候,不是随便分配的,是按照对齐保证整数倍来分配内存地址的。
1. 对于任何类型的变量x,unsafe.Alignof(x)最小为1
2. 对于结构体类型变量x,unsafe.Alignof(x)的数值是结构体中所有字段的对齐保证中的最大值( the largest ),作为结构体的对齐保证,最小为1.
3. 对于数组类型变量x, unsafe.Alignof(x)的结果和数组元素的类型的对齐保证一致.
Go只是规定了对齐保证的基本规则,但是对于不同编译器不同的架构甚至于同一个编译器的不同版本,实现的对齐保证都会有一定的差异,了解规则即可.
每个类型有两个对齐保证。
- 当它被用作结构体类型的字段的字段类型时的对齐保证称为此类型的字段对齐保证。
- 其他情形的对齐保证称为此类型的一般对齐保证。
func main() {
var i bool = true
var j int = 40
fmt.Println("bool-size", unsafe.Alignof(i)) //一般对齐保证
fmt.Println("int-size", unsafe.Alignof(j))
type temp struct {
a bool
b int
}
var tmp = temp{}
fmt.Println("a", unsafe.Alignof(tmp.a)) //字段对齐保证
fmt.Println("b", unsafe.Alignof(tmp.b))
}
输出结果:
bool-size 1
int-size 8
a 1
b 8
实例应用:
内存对齐遵循的规则
变量的存储起始地址一定是对齐保证的整数倍
变量的大小是对齐保证的整数倍,所有的类型都要遵守一规则。
type T struct {
a bool
b int8
c uint16
d uint32
e int64
f bool
}
func main() {
var t = T{}
fmt.Println("t占用的实际内存大小:", unsafe.Sizeof(t), "字节,结构体对齐保证:", unsafe.Alignof(t))
fmt.Println("a:", unsafe.Sizeof(t.a), "字节,字段对齐保证:", unsafe.Alignof(t.a), ",偏移地址:", unsafe.Offsetof(t.a))
fmt.Println("b:", unsafe.Sizeof(t.b), "字节,字段对齐保证:", unsafe.Alignof(t.b), ",偏移地址:", unsafe.Offsetof(t.b))
fmt.Println("c:", unsafe.Sizeof(t.c), "字节,字段对齐保证:", unsafe.Alignof(t.c), ",偏移地址:", unsafe.Offsetof(t.c))
fmt.Println("d:", unsafe.Sizeof(t.d), "字节,字段对齐保证:", unsafe.Alignof(t.d), ",偏移地址:", unsafe.Offsetof(t.d))
fmt.Println("e:", unsafe.Sizeof(t.e), "字节,字段对齐保证:", unsafe.Alignof(t.e), ",偏移地址:", unsafe.Offsetof(t.e))
fmt.Println("f:", unsafe.Sizeof(t.f), "字节,字段对齐保证:", unsafe.Alignof(t.f), ",偏移地址:", unsafe.Offsetof(t.f))
fmt.Println(uintptr(unsafe.Pointer(&t)))
}
输出结果:
t占用的实际内存大小: 24 字节,结构体对齐保证: 8
a: 1 字节,字段对齐保证: 1 ,偏移地址: 0
b: 1 字节,字段对齐保证: 1 ,偏移地址: 1
c: 2 字节,字段对齐保证: 2 ,偏移地址: 2
d: 4 字节,字段对齐保证: 4 ,偏移地址: 4
e: 8 字节,字段对齐保证: 8 ,偏移地址: 8
f: 1 字节,字段对齐保证: 1 ,偏移地址: 16
824634834128
如果按照上面输出的字段大小累加(1+1+2+4+8+1 = 17字节)明显和实际内存大小24字节不符合.现在来一步步解析上面的结果是怎么出来的.
以此类推
最后一个变量f完美分配完成,但是这个时候整个结构体类型还需要满足一个条件,类型的大小需要是对齐保证的整数倍.
结构体的对齐保证是所有字段对齐保证中的最大值,这里为8.而分配的内存大小只有17字节,不满足整数倍的关系.需要填充7字节,保证这个关系
最终结构体t的内存分配完成为24字节,符合我们的打印输出
结构体在内存中完整的存储图示:
举一反三:这里把结构体T中c和d字段的位置替换下,定义结构体R,再来验证下上面分析规则.
输出结果:
package main
import (
"fmt"
"unsafe"
)
type R struct {
a bool
b int8
d uint32
c uint16
e int64
f bool
}
func main() {
var r = R{}
fmt.Println("r占用的实际内存大小:", unsafe.Sizeof(r), "字节,结构体对齐保证:", unsafe.Alignof(r))
fmt.Println("a:", unsafe.Sizeof(r.a), "字节,字段对齐保证:", unsafe.Alignof(r.a), ",偏移地址:", unsafe.Offsetof(r.a))
fmt.Println("b:", unsafe.Sizeof(r.b), "字节,字段对齐保证:", unsafe.Alignof(r.b), ",偏移地址:", unsafe.Offsetof(r.b))
fmt.Println("d:", unsafe.Sizeof(r.d), "字节,字段对齐保证:", unsafe.Alignof(r.d), ",偏移地址:", unsafe.Offsetof(r.d))
fmt.Println("c:", unsafe.Sizeof(r.c), "字节,字段对齐保证:", unsafe.Alignof(r.c), ",偏移地址:", unsafe.Offsetof(r.c))
fmt.Println("e:", unsafe.Sizeof(r.e), "字节,字段对齐保证:", unsafe.Alignof(r.e), ",偏移地址:", unsafe.Offsetof(r.e))
fmt.Println("f:", unsafe.Sizeof(r.f), "字节,字段对齐保证:", unsafe.Alignof(r.f), ",偏移地址:", unsafe.Offsetof(r.f))
fmt.Println(uintptr(unsafe.Pointer(&r)))
}
输出结果:
r占用的实际内存大小: 32 字节,结构体对齐保证: 8
a: 1 字节,字段对齐保证: 1 ,偏移地址: 0
b: 1 字节,字段对齐保证: 1 ,偏移地址: 1
d: 4 字节,字段对齐保证: 4 ,偏移地址: 4
c: 2 字节,字段对齐保证: 2 ,偏移地址: 8
e: 8 字节,字段对齐保证: 8 ,偏移地址: 16
f: 1 字节,字段对齐保证: 1 ,偏移地址: 24
824634834120