Go-unsafe详解

news2024/9/21 5:30:32

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 main

import (
	"fmt"
	"unsafe"
)

func main() {
	var a  = 4.5
	println(Float64bits(a))

	var b = []string{"1","2"}
	bits := SliceBits(b)
	fmt.Printf("%v \n",bits)

	var c = true
	println(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还是uintptr
  println(*(*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() // 返回值为 uintptr
	reflect.ValueOf(p).UnsafePointer() // 返回值为 unsafe.Pointer
	reflect.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" // 创建str
	s := *(*reflect.StringHeader)(unsafe.Pointer(&a)) // 转出stringHead
	header := reflect.SliceHeader{ // 构建sliceHeader
		Data: s.Data,
		Len:  s.Len,
		Cap:  s.Len,
	}
	i := *(*[]byte)(unsafe.Pointer(&header)) // 转为[]byte
	fmt.Printf("%+v \v",i)
}

例子

int转char
func main() {
 	var a = 65
	s := *(*[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 main

import (
	"fmt"
	"unsafe"
)

type Point struct {
	x, y int
}

func main() {
	var p Point
	size := unsafe.Sizeof(p)
	fmt.Println(size) // Output: 16

	var 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 Set

	var 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 Point
	offset := 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 main

import (
	"unsafe"
)

type Point struct {
	x, y int
}

func main() {
	var p = Point{
		x: 123,
		y: 3123,
	}
	pointer := unsafe.Pointer(&p)  // 将p的指针转换为 pointer
	yPointer := 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] = 3
	fmt.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] = 9
	fmt.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.coloradmin.cn/o/659814.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小白必看!渗透测试的8个步骤

渗透测试与入侵的区别 渗透测试:以安全为基本原则,通过攻击者以及防御者的角度去分析目标所存在的安全隐患以及脆弱性,以保护系统安全为最终目标。 入侵:通过各种方法,甚至破坏性的操作,来获取系统权限以…

C++ 教程(15)——数组(包含实例)

C 支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声…

[架构之路-215]- 系统分析-领域建模基本概念

目录 1. 什么是领域或问题域 2. 什么面向对象的“类” 》 设计类 3. 什么是概念类 4. 什么是领域建模 5. 领域建模与DDD(领域驱动架构设计)的关系 6. 领域建模的UML方法 7. 领域建模的案例 其他参考: 1. 什么是领域或问题域 领域&a…

Spring AOP之MethodInterceptor原理

文章目录 引言Spring AOP组成先看一下Advice 示例提问 原理 引言 之前我们讨论过了HandlerInterceptor,现在我们来看一下MethodInterceptor。 MethodInterceptor是Spring AOP中的一个重要接口,用来拦截方法调用,它只有一个invoke方法。 Spring AOP组成…

Laya3.0游戏框架搭建流程(随时更新)

近两年AI绘图技术有了长足发展,准备把以前玩过的游戏类型重制下,也算是圆了一个情怀梦。 鉴于unity商用水印和启动时间的原因,我决定使用Laya来开发。目前laya已经更新到了3.0以上版本,就用目前比较新的版本。 之后关于开发中遇到…

HashMap学习:1.7 迁移死循环分析(通俗易懂)

前言 JDK1.7由于采用的头插法,所以多线程情况下可能会产生死循环问题。 正文 头插法 就是每次从旧容器中的hash桶中取出数据后,放到新容器的头节点问题,如果此时头结点位置为空,直接放置即可,如果不为空将头节点的数…

C语言strncpy的使用缺陷和实现,strncat的使用缺陷和实现,strncmp的使用和实现。

1.strncpy 函数原型: char *strncpy( char *strDest, const char *strSource, size_t count );char *strDest 目标字符串首元素地址const char *strSource 源字符串(需要拷贝过去的字符串)size_t count 拷贝字符的个数char *strncpy 拷贝结束后,返回目…

Micormeter实战

Micrometer 为基于 JVM 的应用程序的性能监测数据收集提供了一个通用的 API,支持多种度量指标类型,这些指标可以用于观察、警报以及对应用程序当前状态做出响应。 前言 可接入监控系统 监控系统的三个重要特征: 维度(Dimensio…

[保姆教程] Windows平台OpenCV以及它的Golang实现gocv安装与测试(亲测通过)

一、MinGW & CMake 预备步骤 首先打开cmd: c: md mingw-w64 md cmake下载安装MinGW-W64 访问: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/7.3.0/ 下载: MinGW-W64 GCC-8…

一文详解Softmax的性质与Self-Attention初步解析

概述 最近研究超平面排列(Hyperplane Arrangement)问题的时候,发现ReLU有其缺陷,即举例来说,ReLU 无法使用单一的超平面将分离的所有数据,完整的输出,即只会输出半个空间映射的数据,而另一半空间的数据被置…

面试---简历

项目 1.1、商品管理 新增商品 同时插入商品对应的使用时间数据,需要操作两张表:product,product_usetime。在productService接口中定义save方法,该方法接受一张Dto对象,dto对象继承自product类,并将prod…

学习open62541 --- [78] 单线程和多线程的使用场景

open62541提供多线程功能,默认不开启,即单线程, 把UA_MULTITHREADING的值设置为 > 100就可以开启多线程了。 这里单线程/多线程的意思是基于open62541运行的server内部是否使用锁去保护数据。只要server运行后还有读写操作需要做&#x…

从源码全面解析 dubbo 消费端服务调用的来龙去脉

👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主📕系列专栏:Java设计模式、Spring源码系列、Netty源码系列、Kafka源码系列、JUC源码…

3D 顶点着色与Phong 反射模型

Phong 反射模型有时被称为“Phong 照明”或“Phong 照明”。它由环境光照、漫反射(朗伯反射)、镜面反射三部分组成。 Phong 反射模型提供了一个方程式,用于计算表面上每个点的光照,I_p: 第一部分代表环境光项。在GLSL代…

X书hmac参数

被删重新发送 全文可以查看: 上面一遍unidbg解密shield文章 unidbg - 》 callObjectMethodV方法填写你的小红书路径下s.xml里的值: 或者在抓包响应头中: 查找xy-ter-str hmac 结果都是在,响应头里,所以 hmac 是服务器下发给客户端的. Over…

黑马头条.

文章目录 前言一、项目概述1.1 能收获什么1.2 项目概述1.3 项目术语1.4 业务说明 二、技术栈2.1技术栈整体框架图2.2技术栈简介 三、nacos环境搭建3.1 虚拟机镜像准备3.2 nacos的安装 四、初始工程搭建4.1 开发环境准备 五、实现登录功能5.1 需求分析5.2 表结构分析5.3 思路分析…

22道常见RocketMQ面试题以及答案

面试宝典到手,搞定面试,不再是难题,系列文章传送地址,请点击本链接。 1、RocketMQ是什么? 2、RocketMQ有什么作用? 3、RoctetMQ的架构 4、RoctetMQ的优缺点 8、消息过滤,如何实现? 9、消息去重,如果…

Elasticsearch 基本使用(四)聚合查询

聚合查询 概述单字段聚合查询统计分组后的数量非文档字段分组文档字段分组 其他聚合运算统计平均值统计总金额统计最大值自定义聚合结果排序简单聚合小结 多字段聚合查询 概述 说到聚合查询,马上会想到 SQL 中的 group by,ES中也有类似的功能&#xff0…

编程语言发展历史

文章目录 语言的发展时间轴语言世代时间轴1940年前-机器语言时代1940年后-汇编语言时代1950年-高级语言的初生1960年-高级语言的进一步成熟1980年-各大语言的进一步增强1990年代-飞速发展时代2000年-新时代 高级编程语言的分类解释型与编译型面向过程与面向对象 对语言的评价Ti…

【学习学习】NLP理解层次模型

NLP(Neuro-Linguistic Programming,神经语言程序学),由两位美国人理查得.班德勒(Richard Bandler)与约翰.葛瑞德(John Grinder)于1976年创办,并在企业培训中广泛使用。美…