## 13.1. unsafe.Sizeof, Alignof 和 Offsetof
unsafe.Sizeof函数返回操作数在内存中的字节大小,参数可以是任意类型的表达式,但是它并不会对表达式进行求值。一个Sizeof函数调用是一个对应uintptr类型的常量表达式,因此返回的结果可以用作数组类型的长度大小,或者用作计算其他的常量。
```Go
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等较大的数据类型最多也只是8字节对齐。
由于地址对齐这个因素,一个聚合类型(结构体或数组)的大小至少是所有字段或元素大小的总和,或者更大因为可能存在内存空洞。内存空洞是编译器自动添加的没有被使用的内存空间,用于保证后面每个字段或元素的地址相对于结构或数组的开始地址能够合理地对齐(译注:内存空洞可能会存在一些随机数据,可能会对用unsafe包直接操作内存的处理产生影响)。
类型 | 大小
------------------------------- | -----------------------------
`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%的内存。
```Go
// 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
```
关于内存地址对齐算法的细节超出了本书的范围,也不是每一个结构体都需要担心这个问题,不过有效的包装可以使数据结构更加紧凑(译注:未来的Go语言编译器应该会默认优化结构体的顺序,当然应该也能够指定具体的内存布局,相同讨论请参考 [Issue10014](https://github.com/golang/go/issues/10014) ),内存使用率和性能都可能会受益。
`unsafe.Alignof` 函数返回对应参数的类型需要对齐的倍数. 和 Sizeof 类似, Alignof 也是返回一个常量表达式, 对应一个常量. 通常情况下布尔和数字类型需要对齐到它们本身的大小(最多8个字节), 其它的类型对齐到机器字大小.
`unsafe.Offsetof` 函数的参数必须是一个字段 `x.f`, 然后返回 `f` 字段相对于 `x` 起始地址的偏移量, 包括可能的空洞.
图 13.1 显示了一个结构体变量 x 以及其在32位和64位机器上的典型的内存. 灰色区域是空洞.
```Go
var x struct {
a bool
b int16
c []int
}
```
下面显示了对x和它的三个字段调用unsafe包相关函数的计算结果:
![](https://box.kancloud.cn/2016-01-10_5691fbe50e4ff.png)
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包,但是这几个函数调用并不是真的不安全,特别在需要优化内存空间时它们返回的结果对于理解原生的内存布局很有帮助。
- 前言
- Go语言起源
- Go语言项目
- 本书的组织
- 更多的信息
- 致谢
- 入门
- Hello, World
- 命令行参数
- 查找重复的行
- GIF动画
- 获取URL
- 并发获取多个URL
- Web服务
- 本章要点
- 程序结构
- 命名
- 声明
- 变量
- 赋值
- 类型
- 包和文件
- 作用域
- 基础数据类型
- 整型
- 浮点数
- 复数
- 布尔型
- 字符串
- 常量
- 复合数据类型
- 数组
- Slice
- Map
- 结构体
- JSON
- 文本和HTML模板
- 函数
- 函数声明
- 递归
- 多返回值
- 错误
- 函数值
- 匿名函数
- 可变参数
- Deferred函数
- Panic异常
- Recover捕获异常
- 方法
- 方法声明
- 基于指针对象的方法
- 通过嵌入结构体来扩展类型
- 方法值和方法表达式
- 示例: Bit数组
- 封装
- 接口
- 接口是合约
- 接口类型
- 实现接口的条件
- flag.Value接口
- 接口值
- sort.Interface接口
- http.Handler接口
- error接口
- 示例: 表达式求值
- 类型断言
- 基于类型断言识别错误类型
- 通过类型断言查询接口
- 类型分支
- 示例: 基于标记的XML解码
- 补充几点
- Goroutines和Channels
- Goroutines
- 示例: 并发的Clock服务
- 示例: 并发的Echo服务
- Channels
- 并发的循环
- 示例: 并发的Web爬虫
- 基于select的多路复用
- 并发的退出
- 示例: 聊天服务
- 基于共享变量的并发
- 竞争条件
- sync.Mutex互斥锁
- sync.RWMutex读写锁
- 内存同步
- 竞争条件检测
- 示例: 并发的非阻塞缓存
- Goroutines和线程
- 包和工具
- 包简介
- 导入路径
- 包声明
- 导入声明
- 包的匿名导入
- 包和命名
- 工具
- 测试
- go test
- 测试函数
- 测试覆盖率
- 基准测试
- 剖析
- 示例函数
- 反射
- 为何需要反射?
- reflect.Type和reflect.Value
- Display递归打印
- 示例: 编码S表达式
- 通过reflect.Value修改值
- 示例: 解码S表达式
- 显示一个类型的方法集
- 几点忠告
- 底层编程
- unsafe.Sizeof, Alignof 和 Offsetof
- unsafe.Pointer
- 示例: 深度相等判断
- 通过cgo调用C代码
- 几点忠告
- 附录
- 附录A:原文勘误
- 附录B:作者译者
- 附录C:译文授权
- 附录D:其它语言