go unsafe包使用

转载自stefno的博客

什么是 unsafe
前面所说的指针是类型安全的,但它有很多限制。Go 还有非类型安全的指针,这就是 unsafe 包提供的 unsafe.Pointer。在某些情况下,它会使代码更高效,当然,也更危险。

unsafe 包用于 Go 编译器,在编译阶段使用。从名字就可以看出来,它是不安全的,官方并不建议使用。我在用 unsafe 包的时候会有一种不舒服的感觉,可能这也是语言设计者的意图吧。

但是高阶的 Gopher,怎么能不会使用 unsafe 包呢?它可以绕过 Go 语言的类型系统,直接操作内存。例如,一般我们不能操作一个结构体的未导出成员,但是通过 unsafe 包就能做到。unsafe 包让我可以直接读写内存,还管你什么导出还是未导出。


为什么有 unsafe
Go 语言类型系统是为了安全和效率设计的,有时,安全会导致效率低下。有了 unsafe 包,高阶的程序员就可以利用它绕过类型系统的低效。因此,它就有了存在的意义,阅读 Go 源码,会发现有大量使用 unsafe 包的例子。


unsafe 实现原理
我们来看源码:

type ArbitraryType int
type Pointer *ArbitraryType

从命名来看,Arbitrary 是任意的意思,也就是说 Pointer 可以指向任意类型,实际上它类似于 C 语言里的 void*。

unsafe 包还有其他三个函数:

func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr
  • Sizeof 返回类型 x 所占据的字节数,但不包含 x 所指向的内容的大小。例如,对于一个指针,函数返回的大小为 8 字节(64位机上),一个 slice 的大小则为 slice header 的大小。
  • Offsetof 返回结构体成员在内存中的位置离结构体起始处的字节数,所传参数必须是结构体的成员。
  • Alignof 返回 m,m 是指当类型进行内存对齐时,它分配到的内存地址能整除 m。

注意到以上三个函数返回的结果都是 uintptr 类型,这和 unsafe.Pointer 可以相互转换。三个函数都是在编译期间执行,它们的结果可以直接赋给 const 型变量。另外,因为三个函数执行的结果和操作系统、编译器相关,所以是不可移植的。

综上所述,unsafe 包提供了 2 点重要的能力:

任何类型的指针和 unsafe.Pointer 可以相互转换。
uintptr 类型和 unsafe.Pointer 可以相互转换。
在这里插入图片描述

还有一点要注意的是,uintptr 并没有指针的语义,意思就是 uintptr 所指向的对象会被 gc 无情地回收。而 unsafe.Pointer 有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收。


unsafe使用实例

package main

import (
	"fmt"
	"let_me_go/network"
	"reflect"
	"unsafe"
)

// 测试unsafe包
func main() {
	ip := new(network.IP)
	ip.SetHost("127.0.0.1")
	ip.SetPort(8080)

	fmt.Println(*ip)

	// 测试unsafe 访问/修改 结构体私有属性
	i := (*string)(unsafe.Pointer(ip))
	fmt.Println(*i)
	*i = "192.168.1.1"
	fmt.Println(*ip)

	// 进行指针偏移
	// 使用unsafe.Sizeof计算结构体每个成员变量所占的地址位
	t := (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(ip)) + unsafe.Sizeof(ip.GetHost())))
	fmt.Println(*t)
	*t = 80
	fmt.Println(*ip)

	// 使用unsafe 进行string和slice的相互转换
	s := "hello world"
	bytes := string2Bytes(s)
	for _, value := range bytes {
		fmt.Println(value)
	}

	fmt.Println(bytes2String(bytes))
}

func string2Bytes(s string) []byte {
	stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))

	sliceHeader := reflect.SliceHeader{
		Data: stringHeader.Data,
		Len:  stringHeader.Len,
		Cap:  stringHeader.Len,
	}

	return *(*[]byte)(unsafe.Pointer(&sliceHeader))
}

func bytes2String(bytes []byte) string {
	sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))

	stringHeader := reflect.StringHeader{
		Data: sliceHeader.Data,
		Len:  sliceHeader.Len,
	}

	return *(*string)(unsafe.Pointer(&stringHeader))
}

运行结果

{127.0.0.1 8080}
127.0.0.1
{192.168.1.1 8080}
8080
{192.168.1.1 80}
104
101
108
108
111
32
119
111
114
108
100
hello world
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值