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 ...
随机推荐
- 视觉目标跟踪算法——SRDCF算法解读
首先看下MD大神2015年ICCV论文:Martin Danelljan, Gustav Häger, Fahad Khan, Michael Felsberg. "Learning Spa ...
- 面试官:说说你对css效率的理解
大家好,我是小雨小雨,致力于分享有趣的.实用的技术文章. 内容分为翻译和原创,如果有问题,欢迎随时评论或私信,希望和大家一起进步. 大家的支持是我创作的动力. 选择器的优先级 众所周知,选择器是有权重 ...
- LeetCode | 707. 设计链表
设计链表的实现.您可以选择使用单链表或双链表.单链表中的节点应该具有两个属性:val 和 next.val 是当前节点的值,next 是指向下一个节点的指针/引用.如果要使用双向链表,则还需要一个属性 ...
- Selenium系列(六) - 强制等待、隐式等待、显式等待
如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...
- JSP(三)----EL表达式
## EL表达式 1.概念:Expression alnguage 表达式语言 2.作用:替换和简化JSP页面中java代码的编写 3.语法:${表达式} 4.注意: * jsp默认支持EL表 ...
- CF1327D Infinite Path 题解
原题链接 太坑了我谔谔 简要题意: 求一个排列的多少次幂能达到另一个排列.排列的幂定义见题.(其实不是新定义的,本来就是这么乘的) 很显然,这不像快速幂那样可以结合律. 既然这样,就从图入手. 将 \ ...
- 聊聊用Selenium做自动化碰到了哪些坑?都是怎么解决的?
本周我们的讨论话题是关于Selenium自动化: 话题:聊聊用Selenium做自动化碰到了哪些坑?都是怎么解决的? 话题描述:Selenium是大家做UI自动化用到的主流框架,在平时写脚本的过程中, ...
- css清除浮动影响
将清除浮动代码添加到重置样式表中,随时可以调用 }}.clearfix:after{clear:both} 给需要清除浮动影响的元素添加class名 --- clearfix 例: <!-- c ...
- Building Applications with Force.com and VisualForce(Dev401)(十三):Implementing Business Processes:Automating Business Processes Part II
ev401-014:Implementing Business Processes:Automating Business Processes Part II Module Agenda1.Multi ...
- Selenium系列(二十) - PageObject模式的详细介绍
如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...