Go语言的语言特性, 隐藏许多细节:
Go语言设计的一些安全机制将错误检查提前到了编译期,如类型检查错误。
数组越界、空指针等错误在运行时检查,发现错误立即终止,通过Go的内存管理、gc可以消除大部分的野指针和内存泄漏。
无法知道一个结构体真实的内存布局,也无法获取一个运行时函数对应的机器码,也无法知道当前的goroutine是运行在哪个操作系统线程之上
Go的调度器处理goroutine和线程的调度关系, gc会根据需要移动指针的内存地址。
有时候需要更好的性能, 这就设计底层的操作,unsafe可以摆脱Go的规则(正常不建议使用!), unsafe包由编译器实现,供了一些访问语言内部特性的方法,特别是内存布局相关的细节。被广泛的使用在runtime
、os
、syscall
还有net
等涉及与os相关的包。
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等较大的数据类型最多也只是8字节对齐。
聚合类型(结构体或数组)的大小至少是所有字段或元素大小的总和或者更大因为可能存在内存空洞。
内存空洞是编译器自动添加的没有被使用的内存空间,用于保证后面每个字段或元素的地址相对于结构或数组的开始地址能够合理地对齐
内存空洞可能会存在一些随机数据,可能会对用unsafe包直接操作内存的处理产生影响)。
Go语言内置类型对应的大小
类型 | 大小 |
---|---|
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语言的规范并没有要求一个字段的声明顺序和内存中的顺序是一致的,所以理论上一个编译器可以随意地重新排列每个字段的内存位置, 本书的时候编译器还没有这么做:
// 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()
unsafe.Offsetof()
unsafe.Pointer()