引言

Golangunsafe.Pointeruintptrunsafe.SizeofGolangunsafe

unsafe包

unsafeunsafeGo
JavaunsafeunsafeGo

unsafe构成

可以看到,包的构成比较简单,下面我们主要结合源码中注释内容来展开剖析和学习。

type ArbitraryType int

Arbitrary
ArbitraryTypeunsafeGo

type Pointer *ArbitraryType

Pointerunsafe

灵活转换

它表示指向任意类型的指针,有四种特殊操作可用于类型指针,而其他类型不可用,大概的转换关系如下:

PointerPointeruintptrPointerPointeruintptr

潜在的危险性

Pointer

源码注释中列举了提到了一些正确错误使用的例子。它还提到更为重要的一点是:不使用这些模式的代码可能现在或者将来变成无效。即使下面的有效模式也有重要的警告。试图来理解下这句话的核心就是,它不能对你提供什么保证!

go vetgo vet
go vetgolangfmt.Printf%dgo vet

代码样例:
func TestErr(t *testing.T) {
  fmt.Printf("%d","hello world")
}
运行:
`go vet unsafe/unsafe_test.go`
控制台输出提示: 
unsafe/unsafe_test.go:9:2: Printf format %d has arg "hello world" of wrong type string

✅ 正确的使用姿势

Pointer
  • (1) 指针 *T1 转化为 指针 *T2. T1、T2两个变量共享等值的内存空间布局,在不超过数据范围的前提下,可以允许将一种类型的数据重新转换、解释为其他类型的数据。

下面我们操作一个样例:声明并开辟一个内存空间,然后基于该内存空间进行不同类型数据的转换。

代码如下:

Pointer

(2) Pointer 转换为 uintptr(不包括返回的转换)

uintptruintptruintptruintptruintptruintptruintptruintptruintptr

uintptr

小结

最常见的用途是访问结构或数组元素中的字段:

&^

❌ 错误的使用姿势

与C中不同的是,将指针指向到其原始分配结束之后是无效的:

注意,两个转换必须出现在同一个表达式中,它们之间只有中间的算术运算。

请注意,指针必须指向已分配的对象,因此它不能是零。

syscall.SyscalluintptrsyscallSyscalluintptruintptr
uintptr
uintptr

要使编译器识别此模式,转换必须出现在参数列表中:

uintptrPointerReflectReflect.Value.PointerReflect.Value.UnsafeAddr
reflectPointerUnsafeAddruintptrunsafeunsafePointer

与上述情况一样,在转换之前存储结果是无效的

reflect.SliceHeaderreflect.StringHeaderPointerreflect.SliceHeaderreflect.StringHeaderuintptrunsafe
SliceHeaderStringHeaderslicestring
hdr.Datauintptr
reflect.SliceHeaderreflect.StringHeaderslicestring*reflect.SliceHeader*reflect.StringHeader

func Sizeof(x ArbitraryType) uintptr

Sizeofvv

Go语言中非聚合类型通常有一个固定的大小
引用类型或包含引用类型的大小在32位平台上是4字节,在64位平台上是8字节

类型分类大小
bool非聚合1个字节
intN, uintN, floatN, complexN非聚合N/8个字节(例如float64是8个字节)
int, uint, uintptr非聚合1个机器字 (32位系统:1机器字=4字节; 64位系统:1机器字=8字节)
*T聚合1个机器字
string聚合2个机器字(data,len)
[]T聚合3个机器字(data,len,cap)
map聚合1个机器字
func聚合1个机器字
chan聚合1个机器字
interface聚合2个机器字(type,value)

func Offsetof(x ArbitraryType) uintptr

Offsetofvf

内存对齐 计算机在加载和保存数据时,如果内存地址合理地对齐的将会更有效率。由于地址对齐这个因素,一个聚合类型的大小至少是所有字段或元素大小的总和,或者更大因为可能存在内存空洞。\

内存空洞 编译器自动添加的没有被使用的内存空间,用于保证后面每个字段或元素的地址相对于结构或数组的开始地址能够合理地对齐

bool、string、int16

以上是针对单个结构体内的内存对齐的测试演示,当多个结构体组合在一起时还会产生内存对齐,感兴趣可以自行实践并打印内存偏移量来观察组合后产生的内存空洞。

func Alignof(x ArbitraryType) uintptr

Alignofvvf

不同类型有着不同的内存对齐方式,总体上都是以最小可容纳单位进行对齐的,这样可以在兼顾以最小的内存空间填充来换取内存计算的高效性。

参考