Go 语言和 C 语言类似,结构体的成员必须按照它们的对齐要求存储。编译器会自动填充对齐字节,确保每个字段的起始地址符合 CPU 的对齐规则。
示例
go
package main
import (
"fmt"
"unsafe"
)
type A struct {
a int8
b uint32
c int16
}
func main() {
fmt.Printf("Offsetof(A.a) = %d\n", unsafe.Offsetof(A{}.a))
fmt.Printf("Offsetof(A.b) = %d\n", unsafe.Offsetof(A{}.b))
fmt.Printf("Offsetof(A.c) = %d\n", unsafe.Offsetof(A{}.c))
fmt.Printf("Sizeof(A) = %d\n", unsafe.Sizeof(A{}))
}
/*
Output:
Offsetof(A.a) = 0
Offsetof(A.b) = 4
Offsetof(A.c) = 8
Sizeof(A) = 12
*/
内存布局
因为 b 需要4字节对齐,所以编译器会在 a 之后填充3个字节。又因为结构体的大小必须是其最大对齐方式(4字节)的整数倍,所以最终结构体 A 的大小为12个字节。
优化
go
package main
import (
"fmt"
"unsafe"
)
type AOptimized struct {
a int8
c int16
b uint32
}
func main() {
fmt.Printf("Offsetof(AOptimized.a) = %d\n", unsafe.Offsetof(AOptimized{}.a))
fmt.Printf("Offsetof(AOptimized.b) = %d\n", unsafe.Offsetof(AOptimized{}.b))
fmt.Printf("Offsetof(AOptimized.c) = %d\n", unsafe.Offsetof(AOptimized{}.c))
fmt.Printf("Sizeof(AOptimized) = %d\n", unsafe.Sizeof(AOptimized{}))
}
优化后内存布局
因为 c 需要2字节对齐,所以编译器会在 a 之后填充1个字节。此时结构体的实际大小为8个字节,刚好是最大对齐方式(4字节)的整数倍,所以最终结构体 A 的大小为8个字节。
结论
Go 结构体需要关注对齐
- 和 C 语言一样,Go 结构体会自动填充来对齐字段。
- 不合理的字段排列会浪费内存。
- 使用
unsafe.Sizeof()
和unsafe.Offsetof()
了解结构体的内存布局。
结构体优化可以减少内存占用
- 合理排序字段,减少填充。
合理优化结构体,可以减少内存占用,提高 Go 程序的性能!