golang中unsafe 和 uintptr 指针怎么用

81次阅读
没有评论

共计 2470 个字符,预计需要花费 7 分钟才能阅读完成。

这篇文章将为大家详细讲解有关 golang 中 unsafe 和 uintptr 指针怎么用,丸趣 TV 小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

1.golang 中的指针类型

三个类型
其实指针有三种:
一种是我们常见的 *,用 * 去表示的指针;
一种是 unsafe.Pointer,Pointer 是 unsafe 包下的一个类型;
最后一种是 uintptr,uintptr 这玩意是可以进行运算的也就是可以 ++–;

他们之间有这样的转换关系:
* = unsafe.Pointer = uintptr

有一点要注意的是,uintptr 并没有指针的语义,意思就是 uintptr 所指向的对象会被 gc 无情地回收。而 unsafe.Pointer 有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收。
从这样的关系你大概就可以猜到,我们使用的指针 * p 转换成 Pointer 然后转换 uintptr 进行运算之后再原路返回,理论上就能等同于进行了指针的运算。我们下面就来实践一下。

2. 具体操作

unsafe 操作 slice

func main() {s := make([]int, 10)s[1] = 2
 p :=  s[0]fmt.Println(*p)
 up := uintptr(unsafe.Pointer(p)) // 这里有可能会被回收   所以最好写成  (*int)unsafe.Pointer(uintptr(unsafe.Pointer(p))+unsafe.Sizeof(int(0)))up += unsafe.Sizeof(int(0)) //  这里不是 up++p2 := (*int)(unsafe.Pointer(up))fmt.Println(*p2)}

输出:
0
2

从代码中我们可以看到,我们首先将指针指向切片的第一个位置,然后通过转换得到 uintptr,操作 uintptr + 上 8 位(注意这里不能 ++ 因为存放的是 int,下一个元素位置相隔举例 int 个字节),最后转换回来得到指针,取值,就能取到切片的第二个位置了。

unsafe 操作 struct(可以访问私有属性)
我们知道如果一个结构体里面定义的属性是私有的,那么这个属性是不能被外界访问到的。我们来看看下面这个操作:

package maintype User struct {age intname string}package mainfunc main() {user :=  User{}fmt.Println(user)
 s := (*int)(unsafe.Pointer(user))*s = 15up := uintptr(unsafe.Pointer(user)) + unsafe.Sizeof(int(0))namep := (*string)(unsafe.Pointer(up))*namep =  ljy  
 fmt.Println(user)}

User 是另外一个 basic 包中的结构体,其中的 age 是小写开头的,理论上来说,我们在外部没有办法修改 age 的值,但是经过上面这波操作之后,输出信息是:
{0}
{10 xxx}
也就是说成功操作到了结构体的私有属性。

顺便提一句:创建结构体会被分配一块连续的内存,结构体的地址也代表了第一个成员的地址。

字符串和 byte 数组转换 inplace
我们知道如果将字符串转换成 []byte 非常方便

s :=  123 a := []byte(s)

但是这样需要开辟额外的空间,那么如何实现原地的,不需要拷贝数据的转换呢?
其实从底层的存储角度来说,string 的存储规则和 []byte 是一样的,也就是说,其实指针都是从某个位置开始到一段空间,中间一格一格。所以利用 unsafe 就可以做到。

func main() {s :=  123 a := []byte(s)print(s =   ,  s,  \n)print(a =   ,  a,  \n)
 a2 := (*[]byte)(unsafe.Pointer( s))print(a2 =   , a2,  \n)fmt.Println(*a2)} 输出结果:s = 0xc420055f40a = 0xc420055f60a2 = 0xc420055f40[49 50 51]

我们可以看到 s 和 a 的地址是不一样的,但是 s 和 a2 的地址是一样的,并且 a2 已经是一个 []byte 了。

存在的问题
其实这个转换是存在问题的,问题就在新的 []byte 的 Cap 没有正确的初始化。
我们打印一下 cap 看一下

fmt.Println(“cap a =”, cap(a)) fmt.Println(“cap a2 =”, cap(*a2))  结果是:cap a = 32cap a2 = 17418400

问题的原因
在 src/reflect/value.go 下看

type StringHeader struct {
 Data uintptr
 Len int}type SliceHeader struct {
 Data uintptr
 Len int
 Cap int}

看到其实 string 没有 cap 而 []byte 有,所以导致问题出现,也容易理解,string 是没有容量扩容这个说法的,所以新的 []byte 没有赋值 cap 所以使用了默认值。

问题解决

stringHeader := (*reflect.StringHeader)(unsafe.Pointer( s))bh := reflect.SliceHeader{
 Data: stringHeader.Data,
 Len: stringHeader.Len,
 Cap: stringHeader.Len,}return *(*[]byte)(unsafe.Pointer( bh))

通过重新设置 SliceHeader 就可以完成

关于“golang 中 unsafe 和 uintptr 指针怎么用”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计2470字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)