当前位置: 首页 > 后端技术 > PHP

详细解释Go团队不推荐的unsafe.Pointer

时间:2023-03-30 05:52:39 PHP

大家好,我是建宇。大家在学习Go的时候一定学过“Go的指针不支持指针运算和转换”这个知识点。为什么?首先,Go是静态语言,所有的变量都必须是标量类型。不同的类型不能进行赋值、计算等跨类型的操作。那么指针也对应了相对类型,这也在Compile的静态类型检查范围之内。同时静态语言也称为强类型。也就是说,一旦定义,就无法更改。错误示例funcmain(){num:=5numPointer:=&numflnum:=(*float32)(numPointer)fmt.Println(flnum)}output:#command-line-arguments...:cannotconvertnumPointer(type*int)totype*float32例子中,我们创建了一个值为5,类型为int的num变量,准备干点大事。接下来,我们取了指针的地址,试图将其强制转换为*float32,结果失败了……万能的破墙unsafe针对的是刚才的“错误示例”,我们可以使用今天的英雄unsafe标准库来解决它。这是一个了不起的包裹。在官方的解释中,有这样一个概述:围绕内存安全的操作和围棋程序的类型。它很可能是不可携带的。未包含在Go1兼容性指南中。简单来说就是不建议大家使用,因为不安全(unsafe)。但在特殊场景下,使用它可以打破Go的类型和内存安全机制,让你获得意想不到的效果。unsafe.Pointer为了解决这个问题,需要使用unsafe.Pointer。它表示一个任意类型和可寻址的指针值,可以在不同的指针类型之间进行转换(类似于C语言中void*的用法)。它包含四个核心操作:任何类型的指针值都可以转换为Pointer。指针可以转换为任何类型的指针值。uintptr可以转换为指针。指针可以转换为uintptr。在这一部分中,重点关注第一点和第二点。再想想如何修改“错误的例子”使其生效?修改如下:funcmain(){num:=5numPointer:=&numflnum:=(*float32)(unsafe.Pointer(numPointer))fmt.Println(flnum)}输出结果:0xc4200140b0上述代码中,我们添加零钱。通过unsafe.Pointer的特性修改指针变量,可以完成任意类型(*T)的指针转换。需要注意的是,此时不能对变量进行操作和访问,因为指针地址指向的对象具体类型是未知的。我不知道它是什么类型,以及如何解析它?解析不了,自然就没法改了。unsafe.Offsetof上一节我们修改了普通指针变量。那么它可以做一些更复杂的事情吗?输入Numstruct{istringjint64}funcmain(){n:=Num{i:"EDDYCJY",j:1}nPointer:=unsafe.Pointer(&n)niPointer:=(*string)(unsafe.Pointer(nPointer))*niPointer="炸鱼"njPointer:=(*int64)(unsafe.Pointer(uintptr(nPointer)+unsafe.Offsetof(n.j)))*njPointer=2fmt.Printf("n.i:%s,n.j:%d",n.i,n.j)}输出结果:n.i:Friedfish,n.j:2在分析这段代码做什么之前,我们需要了解结构体的一些基本概念:结构体的成员变量存放在内存中以上是一段连续的记忆。结构的初始地址是第一个成员变量的内存地址。根据结构成员的地址计算偏移量。可以得到其他成员变量的内存地址。回过头来看上面的代码,得到执行流程:修改n.i的值:i为第一个成员变量。所以不需要计算偏移量,直接把指针取出来转成Pointer,再强制转换成string类型的指针值即可。修改n.j值:j为第二个成员变量。在修改内存地址之前需要进行偏移量计算。经过偏移操作,当前地址已经指向第二个成员变量。然后重复转换赋值。详细分析需要注意,这里使用了以下方法(完成偏移量计算的目标):1.uintptr:uintptr是Go的内置类型。返回一个可以存储完整地址的无符号整数。后续常用的指针操作类型uintptruintptr2,unsafe.Offsetof:返回成员变量x在结构体中的偏移量。更具体地说,它返回结构的初始位置和x之间的字节数。需要注意的是入参ArbitraryType代表的是任意类型,不是定义的int。它的实际功能是占位符funcOffsetof(xArbitraryType)uintptr这部分,其实是巧妙利用了Pointer的第三个和第四个特性。这时候,已经可以对变量进行操作了。错误示例funcmain(){n:=Num{i:"EDDYCJY",j:1}nPointer:=unsafe.Pointer(&n)...ptr:=uintptr(nPointer)njPointer:=(*int64)(unsafe.Pointer(ptr+unsafe.Offsetof(n.j)))...}这里有个问题,uintptr类型不能存放在临时变量中。因为从GC的角度来看,uintptr类型的临时变量只是一个无符号整数,并不知道它是一个指针地址。因此,当满足某些条件时,临时变量ptr可能会被垃圾回收,那么接下来的内存操作是不是很玄乎?如有任何问题,欢迎在评论区反馈交流。最好的关系是相互成就。您的好评是创作炸鱼最大的动力。感谢您的支持。文章持续更新中。可以微信搜索【脑补炸鱼】阅读。本文已收录在GitHubgithub.com/eddycjy/blog中。学习Go语言可以看Go学习地图和路线。欢迎星星提醒。总结简单回顾两个知识点,如下:第一,unsafe.Pointer允许你的变量在不同的指针类型之间切换,即可以表示为任何可寻址的指针类型。二是经常使用uintptr配合unsafe.Pointer进行指针运算,非常巧妙。