13.1. unsafe.Sizeof, Alignof 和 Offsetof
unsafe.Sizeof
函数返回操作数在内存的字节大小, 可以是任意类型的表达式, 但是并不会对表达式进行求值. Sizeof
是一个 uintptr 类型的常量表达式, 因此返回的结果可以用着数据的大小, 或者用作计算其他的常量.
import "unsafe"
fmt.Println(unsafe.Sizeof(float64(0))) // "8"
Sizeof
只返回数据结构中固定的部分, 例如字符串中指针和字符串长度部分, 但是并不包含字符串的内容. Go中非聚合类型通常有一个固定的尺寸, 尽管不同工具链的具体大小可能会有所不同. 考虑到可移植性, 引用类型或包含引用类型的大小在32位平台上是4个字节, 在64位平台上是8个字节.
计算机加载和保存数据时, 如果内存地址合理地对齐的将会更有效率. 例如 2 字节大小的 int16 类型应该是偶数, 一个4 字节大小的 rune 类型地址应该是 4 的倍数, 一个 8 字节大小的 float64, uint64 或 64-bit 指针 的地址应该是 8 字节对齐的. 但是对于再大的地址对齐倍数则是不需要的, 即使是 complex128 等较大的数据类型.
由于这个因素,一个聚合类型(结构体或数组)的大小至少是所有字段或元素大小的总和, 或者更大因为可能存在空洞. 空洞是编译器自动添加的没有被使用的空间, 用于保证后面每个字段或元素的地址相对于结构或数组的开始地址能够合理地对齐.
类型 | 大小 |
---|---|
bool | 1字节 |
intN, uintN, floatN, complexN | N/8字节 (例如 float64 是 8字节) |
int, uint, uintptr | 1个机器字 |
*T | 1个机器字 |
string | 2个机器字(data,len) |
[]T | 3个机器字(data,len, cap) |
map | 1个机器字 |
func | 1个机器字 |
chan | 1个机器字 |
interface | 2个机器字(type,value) |
Go的语言规范并没有保证一个字段的声明顺序和内存中的顺序是一致的, 所以理论上一个编译器可以随意地重新排列每个字段的内存布局, 随着在写作本书的时候编译器还没有这么做. 下面的三个结构体有着相同的字段, 但是第一个比另外的两个需要多 50% 的内存.
// 64-bit 32-bit
struct{ bool; float64; int16 } // 3 words 4words
struct{ float64; int16; bool } // 2 words 3words
struct{ bool; int16; float64 } // 2 words 3words
虽然关于对齐算法的细节超出了本书的范围, 也不是每一个结构体都需要担心这个问题, 不过有效的包装可以使数据结构更加紧凑, 内存使用率和性能都可能受益.
unsafe.Alignof
函数返回对应参数的类型需要对齐的倍数. 和 Sizeof 类似, Alignof 也是返回一个常量表达式, 对应一个常量. 通常情况下布尔和数字类型需要对齐到它们本身的大小(最多8个字节), 其它的类型对齐到机器字大小.
unsafe.Offsetof
函数的参数必须是一个字段 x.f
, 然后返回 f
字段相对于 x
起始地址的偏移量, 包括可能的空洞.
图 13.1 显示了一个结构体变量 x 以及其在32位和64位机器上的典型的内存. 灰色区域是空洞.
var x struct {
a bool
b int16
c []int
}
The table below shows the results of applying the three unsafe functions to x itself and to each of its three fields:
下面显示了应用三个函数对 x 和它的三个字段计算的结果:
32位系统:
Sizeof(x) = 16 Alignof(x) = 4
Sizeof(x.a) = 1 Alignof(x.a) = 1 Offsetof(x.a) = 0
Sizeof(x.b) = 2 Alignof(x.b) = 2 Offsetof(x.b) = 2
Sizeof(x.c) = 12 Alignof(x.c) = 4 Offsetof(x.c) = 4
64位系统:
Sizeof(x) = 32 Alignof(x) = 8
Sizeof(x.a) = 1 Alignof(x.a) = 1 Offsetof(x.a) = 0
Sizeof(x.b) = 2 Alignof(x.b) = 2 Offsetof(x.b) = 2
Sizeof(x.c) = 24 Alignof(x.c) = 8 Offsetof(x.c) = 8
虽然它们在不安全的 unsafe 包, 但是这几个函数并不是真的不安全, 特别在需要优化内存空间时它们对于理解原生的内存布局很有帮助.