“ 如何转换你们,俩不同类型的指针?”
a := "Kylin Lab"b := ([]byte)a

可以看到它们都有一个底层数组来存储变量数据,而类型本身只记录这个数组的起始地址。如果采用强制类型转换的方式把a转换为b,那么就会重新分配b使用的底层数组。然后把a的底层数组内容拷贝到b的底层数组。如果字符串内容很多,多占用这许多字节的内存不说,还要耗费时间做拷贝,所以就显得很不合适了。

要是可以让b重复使用a的底层数组,那就好了。强转不行,就到了unsafe上场的时候了~
01
—
指针类型转换
unsafe提供的第一件法宝就是指针类型转换。我们知道像下面这样的指针类型转换是编译不通过的。
a := "Kylin Lab"var b []bytetmp := (*string)(&b)//cannot convert &b (type *[]byte) to type *string
tmp := (*string)(unsafe.Pointer(&b))
type StringHeader struct {Data uintptrLen int}type SliceHeader struct {Data uintptrLen intCap int}

a := "Kylin Lab"var b []bytetmp := (*string)(unsafe.Pointer(&b))*tmp = a

不过还没完,b的容量还为0呢!怎么修改它呢?我们能拿到b的地址,也知道data和len各占8字节(64位下),只要把b的指针加上16字节就是cap的起始地址。可问题是Go语言的指针支持做加减运算吗?不支持!
这时候就要拿出unsafe提供的第二件法宝了!
02
—
指针运算
Go语言不支持指针直接进行运算,也是为了保障程序运行安全,防止出现莫名其妙的、玄之又玄的bug。
(uintptr)(unsafe.Pointer(&b))
(uintptr)(unsafe.Pointer(&b)) + 16
tmp2 := (*int)(unsafe.Pointer((uintptr)(unsafe.Pointer(&b)) + 16))
*tmp2 = len(b)
a := "Kylin Lab"var b []bytetmp := (*string)(unsafe.Pointer(&b))*tmp = atmp2 := (*int)(unsafe.Pointer((uintptr)(unsafe.Pointer(&b)) + (uintptr)16))*tmp2 = len(b)
tmp2 := (uintptr)(unsafe.Pointer(&b)) + 16capPtr := (*int)(unsafe.Pointer(tmp2))
除此之外,这个硬编码的“16”怎么看都显得格外不和谐。有没有什么好方法,可以获取程序运行平台中一个类型的大小呢?这就要用到unsafe提供的第三个法宝了~
03
—
获取大小和偏移
unsafe.Sizeof可以拿到任意类型的大小,unsafe.Alignof可以拿到任意类型的对齐边界。按照reflect.SliceHeader的定义,我们这里可以用unsafe.Sizeof来获取uintptr和int的大小,b的起始地址偏移这么多就是第三个字段Cap的地址了。
a := "Kylin Lab"var b []bytetmp := (*string)(unsafe.Pointer(&b))*tmp = atmp2 := (*int)(unsafe.Pointer((uintptr)(unsafe.Pointer(&b)) + unsafe.Sizeof(uintptr(1)) + unsafe.Sizeof(1)))*tmp2 = len(b)

所以unsafe还有一个unsafe.Offsetof方法可以获得结构体中某个字段距离结构体起始地址的偏移值,这样就可以确定结构体成员正确的位置了。
bPtr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
unsafe.Offsetof(bPtr.Cap)
tmp2 := (*int)(unsafe.Pointer((uintptr)(unsafe.Pointer(&b)) + unsafe.Offsetof(bPtr.Cap)))*tmp2 = len(b)
strHeader := (*reflect.StringHeader)(unsafe.Pointer(&a))sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))
sliceHeader.Data = strHeader.DatasliceHeader.Len = strHeader.LensliceHeader.Cap = strHeader.Len
04
—
关于string
b[0] = ‘k’
a := "Kylin Lab" string字面量c := "Hello " + a //动态拼接的字符串c[0] = 'h' cannot assign to c[0]
a := "Kylin Lab"c := "Hello" + avar s []bytestrHeader := (*reflect.StringHeader)(unsafe.Pointer(&c))sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&s))sliceHeader.Data = strHeader.DatasliceHeader.Len = strHeader.LensliceHeader.Cap = strHeader.Lens[0] = 'h'fmt.Println(c) “hello Kylin Lab”
05
—
unsafe不安全
unsafe赋予我们操作内存的灵活性的同时,也用名字时刻提醒着我们:
你需要自己确保要转换的两种类型底层内存布局是匹配的~
否则——后果自负!
想了解slice就戳这里
想了解内存对齐就戳这里

文章转载自幼麟实验室,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




