Go-unsafe详解

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包中常用的一些函数,使用时需要谨慎,避免出现不可预料的错误。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/30827.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

吴恩达ChatGPT《Prompt Engineering》笔记

ChatGPT 提示词工程师教程 1. 课程介绍 1.1 ChatGPT 相关术语 LLM:Large Language Model,大语言模型 Instruction Tuned LLM:经过指令微调的大语言模型 Prompt:提示词 RLHF:Reinforcement Learning from Human F…

机器视觉初步6:图像分割专题

图像分割是一种图像处理技术,它将图像划分为具有相似特征的区域。常见的图像分割方法包括阈值分割、边缘分割、区域分割、基于阈值的方法、基于边缘的方法、基于区域的方法、聚类分割、基于图论的方法、基于深度学习的方法。 文章目录 1.阈值分割2.边缘分割3.区域分…

CloFormer实战:使用CloFormer实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整算法设置混合精度,DP多卡,EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试热力图可视化展示…

kali常用ping命令探测

ping 判断目标主机网络是否畅通 ping $ip -c 1其中,-c 1 表示发送一个数据包 traceroute 跟踪路由 traceroute $domain ARPING 探测局域网IP ARP(地址解析协议),将IP地址转换成MAC地址arping $ip -c 1 #!/bin/ bash######…

基于matlab使用先导校准来补偿阵列不确定性(附源码)

一、前言 此示例说明如何使用先导校准来提高天线阵列在存在未知扰动时的性能。 原则上,可以轻松设计理想的均匀线性阵列(ULA)来执行阵列处理任务,例如波束成形或到达方向估计。在实践中,没有理想的阵列。例如&#xff…

初识轻量级分布式任务调度平台 xxl-job

文章目录 前言xxl-job的目录结构项目依赖 (父 pom.xml)xxl-job-admin 启动xxl-job-executor-sample (项目使用示例)xxl-job-executor-sample-frameless : 不使用框架的接入方式案例xxl-job-executor-sample-springboot : springboot接入方案案例 xxl-job执行器器启动流程分析调…

linux_centos7.9/ubuntu20.04_下载镜像及百度网盘分享链接

1、镜像下载站点 网易开源镜像:http://mirrors.163.com/ 搜狐开源镜像:http://mirrors.sohu.com/ 阿里开源镜像:https://developer.aliyun.com/mirror/ 首都在线科技股份有限公司:http://mirrors.yun-idc.com/ 常州贝特康姆软件技…

C++【红黑树】

✨个人主页: 北 海 🎉所属专栏: C修行之路 🎃操作环境: Visual Studio 2019 版本 16.11.17 文章目录 🌇前言🏙️正文1、认识红黑树1.1、红黑树的定义1.2、红黑树的性质1.3、红黑树的特点 2、红黑…

三分钟学习一个python小知识1-----------我的对python的基本语法的理解

文章目录 一、变量定义二、数据类型三、条件语句四、循环语句五、函数定义总结 一、变量定义 在Python中,使用等号()进行变量的定义,并不需要声明变量的类型,Python会自动根据赋值的数据类型来判断变量的类型&#xf…

chatgpt赋能python:Python构造和析构:介绍和实例

Python 构造和析构:介绍和实例 当你编写 Python 程序时,你可能会注意到一个名为构造函数和析构函数的概念。这些函数可以在创建和删除一个对象时自动执行一些操作。本文将深入介绍 Python 中的构造和析构概念。 构造函数 Python 使用一种名为 __init_…

戴尔U盘重装系统Win10步骤和详细教程

戴尔电脑深受用户们的喜欢,那么如何使用U盘给戴尔电脑重装Win10系统呢,这让很多用户都犯难了,以下就是小编给大家分享的戴尔U盘重装系统Win10步骤和详细教程,按照这个教程操作,就能顺利完成戴尔U盘重装Win10系统的操作…

3、互联网行业及产品经理分类

上一篇文章:2、产品经理的工作内容_阿杰学编程的博客-CSDN博客 1、产品经理分类 我们把产品经理划分成这样两个大的类型,一个是传统行业的,一个是互联网行业的。这个简单了解一下就行。 这个里面会发现绝大多数也是体育劳动,你比…

Nautilus Chain:模块化Layer3的先行者

“模块化特性的 Nautilus Chain 正在成为 Layer3 的早期定义者之一,并有望进一步推动区块链更广泛的应用与实践 ” 自以太坊创始人 Vitalik Buterin 在去年提出 Layer3 的概念后,行业始终对“Layer3”进行讨论,并期望推动该概念,从…

微服务框架

流量入口Nginx 在上图中可以看到,Nginx作为整个架构的流量入口,可以理解为一个外部的网关,它承担着请求的路由转发、负载均衡、动静分离等功能。作为一个核心入口点,Nginx肯定要采用多节点部署,同时通过keepalived来实…

【云原生 · Docker】轻松学会dockerfile构建镜像

目录 🍉dockerfile是什么 🍉镜像的缓存特性 🍉dockerfile命令 🍒FROM 🍒RUN 🍒CMD 🍒LABEL 🍒EXPOSE 🍒ENV 🍒ADD 🍒COPY 🍒ENTRYPOIN…

Background-1 基础知识 sqli-Labs Less1-Less-4

文章目录 一、Less-1二、Less-2三、Less-3四、Less-4总结 一、Less-1 http://sqli:8080/Less-1/?id1在第一关我们可以尝试增加一个单引号进行尝试 http://sqli:8080/Less-1/?id1错误显示如下: near 1 LIMIT 0,1 at line 1推测语法的结构 select *from where **…

【从零开始学习JAVA | 第六篇】面向对象综合训练

目录 前言: 1.文字版格斗游戏: 2.对象数组1 前言: 前面我们已经讲解了JAVA面向程序对象思想的关键要素:封装。我们将利用本篇进行几个小型的练习,帮助我们更好的理解面向对象编程这种思想。 1.文字版格斗游戏&#x…

SpringSecurity过滤指定url【.antMatchers(***).permitAll()】失效问题

SpringSecurity过滤指定url【.antMatchers(***).permitAll()】失效问题 问题描述 在使用SpringSecurity作为后端验证框架时,遇到配置一些接口不需要token验证,直接放行,但是配置之后没有生效,一直究其原因。 项目配置 因为要进…

ES6相关概念

什么是ES6? ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。 为什么使用 ES6 ? 每一次标准的诞生都意味着语言的完善,功能的加强。JavaScript语言本身也有一些令人不满意的地方。 变量提升特性增加了程序运行…

SpringBoot整合jwt+redis+随机验证码+Vue的登录功能

一、运行效果展示 !注意:前端的Vue项目中要引入element-ui和axios # npm安装element-ui、axios npm insatll element-ui -S npm install axios -S # 在main中引入 // 引入ElementUI import ElementUI from element-ui import element-ui/lib/theme-chalk…