Go-unsafe详解

news/2024/11/26 13:29:48/

Go语言unsafe包

Go语言的unsafe包提供了一些底层操作的函数,这些函数可以绕过Go语言的类型系统,直接操作内存。虽然这些函数很强大,但是使用不当可能会导致程序崩溃或者产生不可预料的行为。因此,使用unsafe包时必须小心谨慎。

此外,他提供了一些方法和类型如下

在这里插入图片描述

其中Pointer的功能是很强大的,reflect包的功能大多基于它实现,具体可看下面内容

类型

type ArbitraryType int // 代表go中任意类型
type IntegerType int   // int 类型
type Pointer *ArbitraryType // 是可以指向ArbitraryType的指针

Pointer详解

关于Pointer有一些规范如下:

  • 任意类型的指针都可以转换为Pointer,同理,Pointer可以转换为任意类型的指针。
  • uintptr可以转换为pointer,pointer也可以转换为uintptr。

需要注意,Pointer可以操作任意内存,使用起来要小心

Pointer的使用有下面的几种情景:

利用Pointer将*T1转换为*T2

ps:需要注意,底层数据长度可以对上,就可以转换,不一定要固定的类型

package mainimport ("fmt""unsafe"
)func main() {var a  = 4.5println(Float64bits(a))var b = []string{"1","2"}bits := SliceBits(b)fmt.Printf("%v \n",bits)var c = trueprintln(BoolBits(c))println(BoolBitsInt(c))
}
func Float64bits(f float64) uint64 {// 将float64转换为uint64// 首先要注意,是指针和指针的转换。return *(*uint64)(unsafe.Pointer(&f))
}
func SliceBits(data []string) [3]int{// 切片的底层结构是一个 uintptr,和两个int,这里就可以将他转换为一个长度为3的数组// 需要注意的是,这样的是可以的,因为他们的底层数据是长度是一样,都是8个字节。return *(*[3]int)(unsafe.Pointer(&data))
}
func BoolBits(data bool) uint8  {return *(*uint8)(unsafe.Pointer(&data))
}
func BoolBitsInt(data bool) int  {return *(*int)(unsafe.Pointer(&data))
}//outPut:
4616752568008179712
[1374389948232 2 2] 
1
286655889801473  // 底层数据不一致,出了问题

解释如下:

在这里插入图片描述

利用Pointer可以来移动指针

func main() {var p = Point{x: 2,y: 3,}yUintptr := uintptr(unsafe.Pointer(&p)) + 8 // 在p的地址上增加了8个偏移量,找到了p,这个时候p还是uintptrprintln(*(*int)(unsafe.Pointer(yUintptr))) // output:3 // 将uintptr转换为*int的指针,然后访问值// 上面的操作相当于// 	println(*(*int)(unsafe.Add(unsafe.Pointer(&p), 8)))// 从p开始,移动8个,然后访问值
}

将reflect.Value.Pointer or reflect.Value.UnsafeAddr方法的返回值转换为Pointer

这底层用的是Pointer来做的

func main() {var p = Point{x: 2,y: 3,}reflect.ValueOf(p).Pointer() // 返回值为 uintptrreflect.ValueOf(p).UnsafePointer() // 返回值为 unsafe.Pointerreflect.ValueOf(p).UnsafeAddr() // 返回值为 uintptr
}

我们看一个reflect.ValueOf()的实现

在这里插入图片描述

将Pointer转换为reflect.SliceHeader和reflect.StringHeader

可以通过这种方式来查看String和slice底层的数据结构,直接做转换.

func main() {var a = "this"s := (*reflect.StringHeader)(unsafe.Pointer(&a))fmt.Printf("%+v \n",s) //&{Data:4304632499 Len:4}var a1 = []string{"1","2","3"}s2 := (*reflect.SliceHeader)(unsafe.Pointer(&a1))fmt.Printf("%+v",s2) // &{Data:1374389961088 Len:3 Cap:3}
}

利用将string转为[]byte或者[]byte转string的时候不copy底层数组

string转[]byte不拷贝底层数组
  1. 验证copy操作

    利用汇编可以看到在转[]byte的时候调用了函数

在这里插入图片描述

在这里插入图片描述

源码链接:https://github.com/golang/go/blob/261e26761805e03c126bf3934a8f39302e8d85fb/src/runtime/string.go#L166

  1. 自己做,省略copy操作

    func main() {var a = "test" // 创建str// 本质来说就是切片和stringHead的转换ints := *(*[2]int)(unsafe.Pointer(&a))fmt.Printf("%+v \n",ints) // [4297030033 4]var b = [3]int{ // 这里我将stringHead和sliceHead看成了int类型的数组,0 index表示data的ptr,1 index为长度,2index为容量ints[0],ints[1],ints[1],}s := (*[]byte)(unsafe.Pointer(&b))fmt.Printf("%+v\n",*s) // [116 101 115 116]
    }
    

在这里插入图片描述

上面的操作就是现将String转为长度为2的数组,然后构建一个长度为3的数组,转为[]byte切片。

ps: 需要注意,在转换的时候只要底层的数据长度能对上,就可以转换

下面的代码是我按照正常流程写的

func main() {var a = "test" // 创建strs := *(*reflect.StringHeader)(unsafe.Pointer(&a)) // 转出stringHeadheader := reflect.SliceHeader{ // 构建sliceHeaderData: s.Data,Len:  s.Len,Cap:  s.Len,}i := *(*[]byte)(unsafe.Pointer(&header)) // 转为[]bytefmt.Printf("%+v \v",i)
}

例子

int转char
func main() {var a = 65s := *(*[1]byte)(unsafe.Pointer(&a)) // int看为长度为1的byte数组fmt.Printf(string(s[0])) // A 65对应的asill码是A
}
字符串转byte
func main() {var a = "test"i := *(*[]byte)(unsafe.Pointer(&a))fmt.Printf("%+v",i) // [116 101 115 116]
}
操作非导出字段

在这里插入图片描述

方法

Sizeof

func Sizeof(v ArbitraryType) uintptr

Sizeof函数返回类型v的大小。ArbitraryType表示任意类型。

package mainimport ("fmt""unsafe"
)type Point struct {x, y int
}func main() {var p Pointsize := unsafe.Sizeof(p)fmt.Println(size) // Output: 16var p1 = ""size1 := unsafe.Sizeof(p1)fmt.Println(size1) // Output: 16   // string对应的结构体是reflect.StringHeader,StringHeader有两个字段 Data:uintptr类型,表示底层数组,Len int类型,表示数组长度,
}

SizeOf返回的是p类型的的大小,也就是Point类型所占的空间大小

int占8个字节,这里有两个变量,x,y结果就是16

顺便来看看,go中用map实现set的时候,v为什么是空结构体?

func main() {var a = []string{"a", "a", "b"}set := Set(a)fmt.Printf("%v \n", set) // outPut Setvar b = struct {}{}sizeof := unsafe.Sizeof(b) println(sizeof)   // output: 0
}
// Set 用泛型实现set
func Set[T comparable](data []T) []T {m := make(map[T]struct{}, len(data))for _, item := range data {m[item] = struct{}{}}res := make([]T, 0, len(m))for t := range m {res = append(res, t)}return res
}

因为空结构体不占内存空间。

Offsetof

func Offsetof(x ArbitraryType) uintptr

Offsetof函数返回结构体字段x相对于结构体起始位置的偏移量。ArbitraryType表示任意类型。

type Point struct {x, y int
}func main() {var p Pointoffset := unsafe.Offsetof(p.y)fmt.Println(offset) // Output: 8
}

Point结构体中,y变量相对于起始位置偏移量为8,因为y前面有x,x为int,int占在64机器上占8个字节

Alignof

func Alignof(v ArbitraryType) uintptr

Alignof函数返回类型v的对齐方式。ArbitraryType表示任意类型。

type Point struct {x, y int
}func main() {align := unsafe.Alignof(Point{})fmt.Println(align) // Output: 8
}

Add

func Add(p unsafe.Pointer, x uintptr) unsafe.Pointer

Add函数返回指针p加上偏移量x后的指针。注意,返回的指针仍然是unsafe.Pointer类型,需要转换为具体的指针类型才能使用。

package mainimport ("unsafe"
)type Point struct {x, y int
}func main() {var p = Point{x: 123,y: 3123,}pointer := unsafe.Pointer(&p)  // 将p的指针转换为 pointeryPointer := unsafe.Add(pointer, unsafe.Sizeof(p.x))   // 在p的指针上,增加了8个字节的长度,其实就是int类型的长度yPtr := (*int)(yPointer)  // 此时pointer已经指向了y,将此pointer转换为 int类型的指针println(*yPtr)  // 通过指针访问y的值}

add方法就是移动指针,在go里面不支持指针的运算,所以不能像c那样直接位移指针,但go作为新时代的c语言,在加上确实有这个需求,就提供了这种方式来操作指针。

Slice

func Slice(p unsafe.Pointer, len, cap int) unsafe.Pointer

Slice函数返回指针p开始的长度为len、容量为cap的切片。注意,返回的指针仍然是unsafe.Pointer类型,需要转换为具体的切片类型才能使用。

func main() {arr := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} //数组data := unsafe.Slice(&arr[0], len(arr)) // 从数组第0个元素开始,长度为len,返回一个地址的切片array := *(*[len(arr)]byte)(data) // 转换为 数组类型fmt.Printf("%v \n",array) // [0 1 2 3 4 5 6 7 8 9]array[5] = 3fmt.Printf("%v \n",array) // [0 1 2 3 4 3 6 7 8 9] 
}

Slice函数返回一个切片,切片的开始位置是底层数组的开始位置,长度和容量都是len

Slice(ptr, len) 相当于 (*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]

或者下面的例子

func main() {arr := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} //数组data := unsafe.Slice(&arr[0], 3) // 从数组第0个元素开始,长度为len,返回一个地址的切片array := *(*[3]byte)(data) // 转换为 数组类型fmt.Printf("%v \n",array) // [0 1 2]array[2] = 9fmt.Printf("%v \n",array) // [0 1 9]]
}
// 从arr创建了一个长度为3的切片

可以看这篇回答 :
How to create an array or a slice from an array unsafe.Pointer in golang?

以上就是unsafe包中常用的一些函数,使用时需要谨慎,避免出现不可预料的错误。


http://www.ppmy.cn/news/453484.html

相关文章

简析FUP LB5CM/LB6CM 落地式低速大容量冷冻离心机

药用离心机有对生产中换批、换品种时有清洗、灭菌要求,而一般用途离心机却不是主要的。尚具备能够真正意义上的 CIP、SIP 功能的离心机几乎没有,设计上缺少清洗、灭菌的装置和工艺接口、验证口等,转鼓内部不易被完全清洗也是主要问题。较多的…

水塔清洗机器人_淮安中央空调冷水塔维护保养方案

淮安中央空调冷水塔维护保养方案 bzewrweirl 中央空调主机怎样保养?1、保证机组周围环境的清洁,定期检查空调安装装置、控制中心功能是否异常、启动器和继电器是否正常运转、压缩机运行状况是否良好等。2、定期检查轴封状况,查看水系统以及热…

2017第15届中国(武汉)国际汽车服务产业博览会会刊(参展商名录)

2017第15届中国(武汉)国际汽车服务产业博览会会刊(参展商名录) 展会名称: 2017年第15届中国(武汉)国际汽车服务产业博览会     所属展馆: 武汉国际博览中心     开展时间: 2017-06-16 至 06-18 参展…

OSChina 周六乱弹 ——还有一口气,我就学好PHP

2019独角兽企业重金招聘Python工程师标准>>> Osc乱弹歌单(2017)请戳(这里) 【今日歌曲】 Lailysh : #毎日の歌# 我本人 —吴雨霏 《我本人》—吴雨霏 手机党少年们想听歌,请使劲儿戳(这里&#…

网络安全等级保护-信息安全管理制度汇编参考

需要网络安全制度汇编请下载 网络安全等级保护-信息安全管理制度汇编参考下载 ,等级保护测评公司,网络安全等级保护测评,等级保护测评机构,等级保护机构 需要加强等网络、信息安全级保护定级备案,网络安全测评及网络安全整改工作。…

系统思考:良性循环,蝴蝶效应

股神巴菲特曾经说过:“人生就像滚雪球,重要的是找到湿的雪和足够长的山坡。”不论个人或组织,都需要像滚雪球一样,找到属于自己的雪种,然后持续的滚动,造成蝴蝶效应的良性循环。 今天要分享的也是系统思考…

Linux Thermal机制源码分析之框架概述

一、概述 Thermal,中文意思是热的、保暖的。在 Linux 内核中,Thermal 特指一套关于温控机制的驱动框架,其目的是为了防止 SoC 等硬件芯片因过热而造成系统不稳定,甚至缩减芯片寿命。 Thermal 框架是在软件层面上对自然界散热系统的…

为什么罐装的自动喷剂喷了一会罐身会变凉?

液体变为气体而得不到外界能量补充时,它的温度会怎样?化装品或杀虫剂之类的自动喷剂,当喷了一会之后,罐身就会变凉。这类罐装的喷剂,都含有产生压力的物质,叫做二氟二氯甲烷(CCl2F2)的物质。它也是冰箱和冷…