Go中的unsafe
unsafe
最近关注了一个大佬的文章,文章写的非常好,大家可以去关注下。 微信公众号【码农桃花源】
- 指针类型
- 我们知道slice 和 map 包含指向底层数据的指针
- 什么是 unsafe
- 为什么会有unsafe
- unsafe.Pointer && uintptr类型
- unsafe.Pointer
- uintptr
- 总结
指针类型
首先我们先来了解下,GO里面的指针类型。
为什么需要指针类型呢?参考文献 go101.org 里举了这样一个例子:
func double(x int) {
fmt.Println(x)
x += x
fmt.Println(x)
} func main() {
var a =
double(a)
fmt.Println(a) }
double函数的作用是将3翻倍,但是实际上却没有做到,为什么呢? 因为go语言的函数操作都是值传递。double函数里面的x只是a的一个拷贝, 在函数内部对x的操作不能反馈到实参a。
其实在实际的编写代码的过程中我们会使用一个指针进行解决。
func double1(x *int) {
*x += *x
x = nil
} func main() {
var a =
double1(&a) fmt.Println(a) p := &a double1(p) fmt.Println(*p) }
其中有一个操作
x=nil
这个操作没有对我们的结果产生丝毫的影响。 其实也是很好理解的,因为我们知道go里面的函数中使用的都是值传递 x=nil,只是对&a的一个拷贝。
我们知道slice 和 map 包含指向底层数据的指针
我们对它们的操作是会影响到,原参数的值。
func change(sl []int64) {
sl[] =
} func main() { var sl = make([]int64, )
change(sl)
fmt.Println(sl) // [2 0]
}
我们而已看到输出的值已经是[2 0]
这时候我们可以使用一个copy来操作
func change(sl []int64) {
sl[] =
} func changeNo(sl []int64) {
s2 := make([]int64, )
copy(sl, s2)
s2[] =
} func main() { var sl = make([]int64, )
change(sl)
fmt.Println(sl) changeNo(sl)
fmt.Println(sl)
}
限制一:GO里面的指针不能进行数学的运算
错误
a :=
p := &a p++
p = &a +
限制二:不同类型的指针不能互相转换
错误的
func main(){
a:=int()
var f *float64 f=&a
}
限制三:不同类型的指针不能使用==或!=比较。
限制四:不能类型的指针变量不能相互赋值。
什么是 unsafe
前面讨论的指针是类型安全的,但它有很多的限制。go还有非类型安全的指针,就是unsafe 包提供的 unsafe.Pointer。在某些情况下,它会使代码更高效,当然,也更危险。
unsafe 包用于 Go 编译器,在编译阶段使用。从名字就可以看出来,它是不安全的,官方并不建议使用。 它可以绕过 Go 语言的类型系统,直接操作内存。
为什么会有unsafe
Go 语言类型系统是为了安全和效率设计的,有时,安全会导致效率低下。有了 unsafe 包,高阶的程序员 就可以利用它绕过类型系统的低效。因此,它就有了存在的意义,阅读 Go 源码,会发现有大量使用 unsafe 包的例子。
unsafe实现原理
我们来看源码:
type ArbitraryType int
type Pointer *ArbitraryType
从命名来看, Arbitrary 是任意的意思,也就是说 Pointer 可以指向任意类型,实际上它类似于 C 语言里的 void*。
unsafe包还有其他三个函数:
func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr
size返回类型x所占的字节数,单不包含x所指向的内容的大小。例如,对于一个指针,函数返回 的大小为8字节(64位机器上),一个slice的大小则为slice header的大小。
offsetof返回结构体在内存中的位置离结构体起始处的字节数,所传参数必须是结构体的成员。
Alignof 返回 m,m 是指当类型进行内存对齐时,它分配到的内存地址能整除 m。
上面三个函数的返回结果都是uintptr类型,这和 unsafe.Pointer 可以相互转换。三个函数都是在编译期间执 行,它们的结果可以直接赋给 const型变量。另外,因为三个函数执行的结果和操作系统、编译器相关,所以是不可 可移值的。
综上,unsafe包提供了2点重要的能力:
、任何类型的指针和unsafe.Point可以相互转换。
、uintptr类型和unsafe.Point可以相互转换
pointer不能直接进行数学运算,但可以把它转换成uintptr,对uintptr类型进行数学运算,在转换成pointer 类型。
// uintptr 是一个整数类型,它足够大,可以存储
type uintptr uintptr
还有一点需要注意的是,uintptr并没有指针的含义,意思是uintptr所指向的对象会被gc给回收掉。 而unsafe.Pointer 有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收。
unsafe.Pointer && uintptr类型
unsafe.Pointer
这个类型比较重要,它是实现定位欲读写的内存的基础。官方文档对该类型有四个重要描述:
()任何类型的指针都可以被转化为Pointer
()Pointer可以被转化为任何类型的指针
()uintptr可以被转化为Pointer
()Pointer可以被转化为uintptr
大多数指针类型都会写成T,表示是“一个指向T类型变量的指针”。unsafe.Pointer是特别定义的一种指 针类型,它可以包含任何类型变量的地址。当然,我们不可以直接通过*p来获取unsafe.Pointer指针指 向的真是变量的值,因为我们并不知道变量的具体类型。和人普通指针一样,unsafe.Pointer指针是可以 比较的,并且支持和nil常量比较判断是否为空指针。
一个普通的的T类型指针可以被转换成unsafe.Pointer类型指针,并且一个unsafe.Pointer类型指针也可以 被转换成普通类型的指针,被转换回普通的指针类型并不需要和原始的T类型相同。
通过将float64类型指针转化为uint64类型指针,我们可以查看一个浮点数变量的位模式。
func Float64bits(f float64) uint64 {
fmt.Println(reflect.TypeOf(unsafe.Pointer(&f))) //unsafe.Pointer
fmt.Println(reflect.TypeOf((*uint64)(unsafe.Pointer(&f)))) //*uint64
return *(*uint64)(unsafe.Pointer(&f))
} func main() {
fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
}
再看一个例子
func main() {
v1 := uint()
v2 := int() fmt.Println(reflect.TypeOf(v1)) //uint
fmt.Println(reflect.TypeOf(v2)) //int fmt.Println(reflect.TypeOf(&v1)) //*uint
fmt.Println(reflect.TypeOf(&v2)) //*int p := &v1 //两个变量的类型不同,不能赋值
//p = &v2 //cannot use &v2 (type *int) as type *uint in assignment fmt.Println(reflect.TypeOf(p)) // *unit
}
当再次把 v2 的指针赋值给p时,会发生错误cannot use &v2 (type *int) as type *uint in assignment,也就是说类型不同,一个是*int,一个是*uint。
可以使用unsafe.Pointer进行转换,如下,
func main() { v1 := uint()
v2 := int() fmt.Println(reflect.TypeOf(v1)) //uint
fmt.Println(reflect.TypeOf(v2)) //int fmt.Println(reflect.TypeOf(&v1)) //*uint
fmt.Println(reflect.TypeOf(&v2)) //*int p := &v1 p = (*uint)(unsafe.Pointer(&v2)) //使用unsafe.Pointer进行类型的转换 fmt.Println(reflect.TypeOf(p)) // *unit
fmt.Println(*p) //
}
uintptr
// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr
uintptr是golang的内置类型,是能存储指针的整型,在64位平台上底层的数据类型是,
typedef unsigned long long int uint64;
typedef uint64 uintptr;
一个unsafe.Pointer指针也可以被转化成uintptr类型,然后保存到指针类型数值变量中(注:这只是和 当前指针相同的一个数字值,并不是一个指针),然后用以做必要的指针数值运算。(uintptr是一个无符号 的整型数,足以保存一个地址)这种转换虽然是可逆的,但是将uintptr转为unsafe.Pointer指针可能破坏 类型系统,因为并不是所有的数字都是有效的内存地址。
许多将unsafe.Pointer指针转化成原生数字,然后再转换成unsafe.Pointer类型指针的操作也是不安全的 。比如下面的例子需要将变量x的地址加上b字段地址偏移量转化为*int16类型指针,然后通过该指针更新x.b:
func main() { var x struct {
a bool
b int16
c []int
} /**
unsafe.Offsetof 函数的参数必须是一个字段 x.f, 然后返回 f 字段相对于 x 起始地址的偏移量, 包括可能的空洞.
*/ /**
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
指针的运算
*/
// 和 pb := &x.b 等价
pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb =
fmt.Println(x.b) // "42"
}
上面的写法尽管很繁琐,但在这里并不是一件坏事,因为这些功能应该很谨慎地使用。不要试图引入一个uintptr类型的临时变 量,因为它可能会破坏代码的安全性(注:这是真正可以体会unsafe包为何不安全的例子)。
下面的这段代码是错误的
// NOTE: subtly incorrect!
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))
*pb =
产生错误的原因很微妙。有时候垃圾回收器会移动一些变量以降低内存碎片等问题。这类垃圾回收 器被称为移动GC。当一个变量被移动,所有的保存改变量旧地址的指针必须同时被更新为变量移动 后的地址。从垃圾收集器的角度看,一个unsafe.Pointer是一个指向变量的指针,因此当变量被 移动是对应的指针也必须被更新;但是uintptr类型的临时变量只是一个普通的数字,所以其值 不应该被改变。上面错误的代码因引入一个非指针的临时变量temp,导致垃圾收集器无法正确识别 这个是一个指向变量x的指针。当第二个语句执行是,变量X可能被转移,这时候临时变量tmp也就是 不再是现在&x.b地址。第三个指向之前无效地址空间的赋值将摧毁整个系统。
总结
unsafe包绕过了GO的类型系统,达到直接操作内存的目的,使用它是有一定风险的。但是在某些场景 下,使用unsafe包函数会提升代码的效率,GO源码中也是大量使用unsafe包。
unsafe 包定义了 Pointer 和三个函数:
func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr
通过三个函数可以获取变量的大小,偏移,对齐等信息。
uintptr可以和unsafe.Pointer进行相互的转换,uintptr可以进行数学运算。这样,通过 uintptr 和 unsafe.Pointer 的结合就解决了 Go 指针不能进行数学运算的限制。
通过 unsafe 相关函数,可以获取结构体私有成员的地址,进而对其做进一步的读写操作,突破 Go 的类型安全限制。
参考
- 【深度解密Go语言之unsafe】 https://mp.weixin.qq.com/s/OO-kwB4Fp_FnCaNXwGJoEw
- 【Go之unsafe.Pointer && uintptr类型】 https://my.oschina.net/xinxingegeya/blog/729673
Go中的unsafe的更多相关文章
- Java中的Unsafe类111
1.Unsafe类介绍 Unsafe类是在sun.misc包下,不属于Java标准.但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty.Hadoo ...
- Java中的Unsafe
在阅读AtomicInteger的源码时,看到了这个类:sum.msic.Unsafe,之前从没见过.所以花了点时间google了一下. Unsafe的源码:http://www.docjar.com ...
- C#中的Unsafe和Fixed
托管代码 (managed code):由公共语言运行库环境(而不是直接由操作系统)执行的代码.托管代码应用程序可以获得公共语言运行库服务,例如自动垃圾回收.运行库类型检查和安全支持等.这些服务帮助提 ...
- C#中的unsafe
为了保持类型安全性,默认情况下,C# 不支持指针算法. 但是,通过使用 unsafe 关键字,可以定义可在其中使用指针的不安全上下文. 有关指针的详细信息,请参阅主题指针类型. 备注 在公共语言运行时 ...
- golang中,unsafe.sizeof到底是干嘛的?
https://www.golangtc.com/t/5ad833404ce40d2654053485 小生初学Go,有一点不懂,今天为了知道空结构体到底占多大的空间的时候,去百度说用unsafe.s ...
- Java中的Unsafe在安全领域的一些应用总结和复现
目录 0 前言 1 基本使用 1.1 内存级别修改值 1.2 创建对象 1.3 创建VM Anonymous Class 2 利用姿势 2.1 修改值以关闭RASP等防御措施 2.2 创建Native ...
- Unity3d中C#使用指针(Unsafe)的办法(转)
近日由于在U3D项目中要使用到数据传递(C++ DLL的数据传递给U3D中的C#),其中涉及到需要使用C#的指针.直接编译会出现以下错误Unsafe code requires the 'unsafe ...
- .NET Core中妙用unsafe减少gc提升字符串处理性能
一.前言 昨天在群里讨论怎么样效率的把一个字符串进行反转,一般的情况我们都知道,只要对String对象进行操作,那么就会生成新的String对象,比如"1"+"2&quo ...
- Conccrent中 Unsafe类原理 以及 原子类AutomicXX的原理以及对Unsafe类的使用
Unsafe类的介绍 Java中基于操作系统级别的原子操作类sun.misc.Unsafe,它是Java中对大多数锁机制实现的最基础类.请注意,JDK 1.8和之前JDK版本的中sun.misc.Un ...
随机推荐
- Dubbo反序列化漏洞(CVE-2019-17564) 重现
1. 下载官方 demo 代码(暴出的漏洞是 http 协议的,故使用 http 的 demo 来重现)https://github.com/apache/dubbo-samples/tree/mas ...
- 软件版本管理工具-SVN
一.SVN简介 Subversion(svn)是一款开发源代码的版本控制系统. repository(源代码库):源代码统一存放的地方 Checkout(检出):当你手上没有源代码的时候,你需要从re ...
- masql数据库的表查询
昨日回顾 表与表之间建关系 一对多 换位思考 图书与出版社 先站在左表: 考虑左表的多条数据能否对应右表的一条数据 翻译:多本书能否被一个出版社出版 可以! 注意:单站在一张得出的表关系并不能明确两张 ...
- [Docker8]Dockerfiles
Comment INSTRUCTION arguments FROM 基于哪个base镜像 RUN 执行命令并创建新的镜像层,run经常用于安装软件包 MAINTAINER 镜像创建者 copy 将文 ...
- 答应我,不会这些概念,简历不要写 “熟悉” zookeeper
整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 一口气说出 9种 分布式ID生成方式,面试官有点懵了 面试总被问 ...
- Oracle 10g客户端的安装和配置
1.双击Oracle11g_database安装目录下的Setup.exe. 2.选择“基本安装”,设置“安装位置”,填写“数据库名”和“口令”,点击“下一步”. 3.点击“下一步”. 4.一般会出现 ...
- 非常诡异的IIS下由配置文件加上svg的mime头导致整个网站的静态文件访问报错误
调试了两天遇到一个非常诡异的问题 一个系统稳定运行了很多年,是用mvc5+WIN2008R2 + .NET 4.5 +IIS环境下运行,非常稳定,最近想迁移到一台新的服务器,为了少麻烦在阿里云上买了 ...
- macbook中出现2003 - Can't connect to MySQL server on '127.0.0.1' (61 "Connection refused") 如何解决
第一步 关闭mysql服务: 苹果->系统偏好设置->最下边点mysql 在弹出页面中 关闭mysql服务(点击stop mysql server) 如果这种方法没有成功: 可以使用命令行 ...
- 运行npm安装wepy2踩坑error EEXIST 问题
windows 10安装wepy2 以前用过wepy1,现在要学习wepy2,运行以下命令出错 npm install @wepy/cli -g # 全局安装 WePY CLI 工具 打开log文件, ...
- 老技术新谈,Java应用监控利器JMX(2)
各位坐稳扶好,我们要开车了.不过在开车之前,我们还是例行回顾一下上期分享的要点. 上期由于架不住来自于程序员内心的灵魂的拷问,于是我们潜心修炼,与 Java 应用监控利器 JMX 正式打了个照面. J ...