You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
uintptr是 Go 语言的内置类型,是能存储指针的整型,在64位平台上底层的数据类型是 uint64。
// uintptr is an integer type that is large enough to hold the bit pattern of// any pointer.typeuintptruintptrtypedefunsignedlonglongintuint64;
typedefuint64uintptr;
随着程序执行的进行,当前goroutine会经常发生栈扩容或者栈缩容,会把旧栈内存的数据拷贝到新栈区然后更改所有指针的指向。一个unsafe.Pointer是一个指针,因此当它指向的数据被移动到新栈区后指针也会被更新。但是uintptr类型的临时变量只是一个普通的数字,所以其值不会该被改变。上面错误的代码因为引入一个非指针的临时变量 tmp,导致系统无法正确识别这个是一个指向变量 x 的指针。当第二个语句执行时,变量 x 的数据可能已经被转移,这时候临时变量tmp也就不再是现在的 &x.b 的地址。第三个语句向之前无效地址空间的赋值语句将让整个程序崩溃。
unsafe 包用于编译阶段可以绕过 Go 语言的类型系统,直接操作内存。例如,利用 unsafe 包操作一个结构体的未导出成员。unsafe 包让我可以直接读写内存的能力。
unsafe包只有两个类型,三个函数,但是功能很强大。
ArbitraryType
是int
的一个别名,在Go中ArbitraryType
有特殊的意义。代表一个任意Go
表达式类型。Pointer
是int
指针类型的一个别名,在Go中可以把任意指针类型转换成unsafe.Pointer
类型。三个函数的参数均是
ArbitraryType
类型,就是接受任何类型的变量。Sizeof
接受任意类型的值(表达式),返回其占用的字节数,这和c语言里面不同,c语言里面sizeof函数的参数是类型,而这里是一个值,比如一个变量。Offsetof
:返回结构体成员在内存中的位置距离结构体起始处的字节数,所传参数必须是结构体的成员(结构体指针指向的地址就是结构体起始处的地址,即第一个成员的内存地址)。struct
类型,且还不能直接将这个struct
类型的变量当作参数,只能将这个struct
类型变量的属性当作参数。注意以上三个函数返回的结果都是 uintptr 类型,这和 unsafe.Pointer 可以相互转换。三个函数都是在编译期间执行
unsafe.Pointer
unsafe.Pointer称为通用指针,官方文档对该类型有四个重要描述:
unsafe.Pointer是特别定义的一种指针类型(译注:类似C语言中的void类型的指针),在Go 语言中是用于各种指针相互转换的桥梁,它可以持有任意类型变量的地址。
什么叫"可以持有任意类型变量的地址"呢?意思就是使用 unsafe.Pointer 转换的变量,该变量一定要是指针类型,否则编译会报错。
和普通指针一样,unsafe.Pointer 指针也是可以比较的,并且支持和
nil
比较判断是否为空指针。unsafe.Pointer 不能直接进行数学运算,但可以把它转换成 uintptr,对 uintptr 类型进行数学运算,再转换成 unsafe.Pointer 类型。
uintptr
uintptr是 Go 语言的内置类型,是能存储指针的整型,在64位平台上底层的数据类型是 uint64。
一个
unsafe.Pointer
指针也可以被转化为uintptr
类型,然后保存到uintptr
类型的变量中(注:这个变量只是和当前指针有相同的一个数字值,并不是一个指针),然后用以做必要的指针数值运算。(uintptr是一个无符号的整型数,足以保存一个地址)这种转换虽然也是可逆的,但是将uintptr转为unsafe.Pointer指针可能会破坏类型系统,因为并不是所有的数字都是有效的内存地址。还有一点要注意的是,uintptr 并没有指针的语义,意思就是存储 uintptr 值的内存地址在Go发生GC时会被回收。而 unsafe.Pointer 有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收。
使用unsafe.Pointer进行指针类型转换
使用unsafe.Pointer 读写结构体的私有成员
通过 Offsetof 方法可以获取结构体成员的偏移量,进而获取成员的地址,读写该地址的内存,就可以达到改变成员值的目的。
这里有一个内存分配相关的事实:结构体会被分配一块连续的内存,结构体的地址也代表了第一个成员的地址。
上面的写法尽管很繁琐,但在这里并不是一件坏事,因为这些功能应该很谨慎地使用。不要试图引入一个uintptr类型的临时变量,因为它可能会破坏代码的安全性
如果改为下面这种用法是有风险的:
随着程序执行的进行,当前goroutine会经常发生栈扩容或者栈缩容,会把旧栈内存的数据拷贝到新栈区然后更改所有指针的指向。一个unsafe.Pointer是一个指针,因此当它指向的数据被移动到新栈区后指针也会被更新。但是uintptr类型的临时变量只是一个普通的数字,所以其值不会该被改变。上面错误的代码因为引入一个非指针的临时变量 tmp,导致系统无法正确识别这个是一个指向变量 x 的指针。当第二个语句执行时,变量 x 的数据可能已经被转移,这时候临时变量tmp也就不再是现在的 &x.b 的地址。第三个语句向之前无效地址空间的赋值语句将让整个程序崩溃。
string 和 []byte 零拷贝转换
这是一个非常精典的例子。实现字符串和 bytes 切片之间的零拷贝转换。
string和[]byte 在运行时的类型表示为
reflect.StringHeader
和reflect.SliceHeader
只需要共享底层 []byte 数组就可以实现零拷贝转换。
代码比较简单,不作详细解释。通过构造
reflect.StringHeader
和reflect.SliceHeader
,来完成 string 和 []byte 之间的转换。The text was updated successfully, but these errors were encountered: