The unsafe Package in Golang

Golang的unsafe包是一个很特殊的包。 为什么这样说呢? 本文将详细解释。

来自go语言官方文档的警告

unsafe包的文档是这么说的:

导入unsafe的软件包可能不可移植,并且不受Go 1兼容性指南的保护。

Go 1 兼容性指南这么说:

导入unsafe软件包可能取决于Go实现的内部属性。 我们保留对可能导致程序崩溃的实现进行更改的权利。

当然包名称暗示unsafe包是不安全的。 但这个包有多危险呢? 让我们先看看unsafe包的作用。

Unsafe包的作用

直到现在(Go1.7),unsafe包含以下资源:

  • 三个函数:

    • func Alignof(variable ArbitraryType)uintptr
    • func Offsetof(selector ArbitraryType)uintptr
    • func Sizeof(variable ArbitraryType)uintptr
  • 和一种类型:

    • 类型Pointer * ArbitraryType

这里,ArbitraryType不是一个真正的类型,它只是一个占位符。

与Golang中的大多数函数不同,上述三个函数的调用将始终在编译时求值,而不是运行时。 这意味着它们的返回结果可以分配给常量。

(BTW,unsafe包中的函数中非唯一调用将在编译时求值。当传递给len和cap的参数是一个数组值时,内置函数和cap函数的调用也可以在编译时被求值。)

除了这三个函数和一个类型外,指针在unsafe包也为编译器服务。

出于安全原因,Golang不允许以下之间的直接转换:

  • 两个不同指针类型的值,例如 int64和 float64。

  • 指针类型和uintptr的值。

但是借助unsafe.Pointer,我们可以打破Go类型和内存安全性,并使上面的转换成为可能。这怎么可能发生?让我们阅读unsafe包文档中列出的规则:

  • 任何类型的指针值都可以转换为unsafe.Pointer。
  • unsafe.Pointer可以转换为任何类型的指针值。
  • uintptr可以转换为unsafe.Pointer。
  • unsafe.Pointer可以转换为uintptr。

这些规则与Go规范一致:

底层类型uintptr的任何指针或值都可以转换为指针类型,反之亦然。

规则表明unsafe.Pointer类似于c语言中的void 。当然,void 在C语言里是危险的!

在上述规则下,对于两种不同类型T1和T2,可以使 T1值与unsafe.Pointer值一致,然后将unsafe.Pointer值转换为 T2值(或uintptr值)。通过这种方式可以绕过Go类型系统和内存安全性。当然,滥用这种方式是很危险的。

举个例子:

package main

import (
"fmt"
"unsafe"
)
func main() {
var n int64 = 5
var pn = &n
var pf = (*float64)(unsafe.Pointer(pn))
// now, pn and pf are pointing at the same memory address
fmt.Println(*pf) // 2.5e-323
*pf = 3.14159
fmt.Println(n) // 4614256650576692846
}

在这个例子中的转换可能是无意义的,但它是安全和合法的(为什么它是安全的?)。

因此,资源在unsafe包中的作用是为Go编译器服务,unsafe.Pointer类型的作用是绕过Go类型系统和内存安全。

再来一点 unsafe.Pointer 和 uintptr

这里有一些关于unsafe.Pointer和uintptr的事实:

  • uintptr是一个整数类型。

    • 即使uintptr变量仍然有效,由uintptr变量表示的地址处的数据也可能被GC回收。
  • unsafe.Pointer是一个指针类型。
    • 但是unsafe.Pointer值不能被取消引用。
    • 如果unsafe.Pointer变量仍然有效,则由unsafe.Pointer变量表示的地址处的数据不会被GC回收。
    • unsafe.Pointer是一个通用的指针类型,就像* int等。

由于uintptr是一个整数类型,uintptr值可以进行算术运算。 所以通过使用uintptr和unsafe.Pointer,我们可以绕过限制,* T值不能在Golang中计算偏移量:

package main

import (
"fmt"
"unsafe"
) func main() {
a := [4]int{0, 1, 2, 3}
p1 := unsafe.Pointer(&a[1])
p3 := unsafe.Pointer(uintptr(p1) + 2 * unsafe.Sizeof(a[0]))
*(*int)(p3) = 6
fmt.Println("a =", a) // a = [0 1 2 6] // ... type Person struct {
name string
age int
gender bool
} who := Person{"John", 30, true}
pp := unsafe.Pointer(&who)
pname := (*string)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.name)))
page := (*int)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.age)))
pgender := (*bool)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.gender)))
*pname = "Alice"
*page = 28
*pgender = false
fmt.Println(who) // {Alice 28 false}
}

unsafe包有多危险

关于unsafe包,Ian,Go团队的核心成员之一,已经确认:

  • 在unsafe包中的函数的签名将不会在以后的Go版本中更改,

  • 并且unsafe.Pointer类型将在以后的Go版本中始终存在。

所以,unsafe包中的三个函数看起来不危险。 go team leader甚至想把它们放在别的地方。 unsafe包中这几个函数唯一不安全的是它们调用结果可能在后来的版本中返回不同的值。 很难说这种不安全是一种危险。

看起来所有的unsafe包的危险都与使用unsafe.Pointer有关。 unsafe包docs列出了一些使用unsafe.Pointer合法或非法的情况。 这里只列出部分非法使用案例:

package main

import (
"fmt"
"unsafe"
) // case A: conversions between unsafe.Pointer and uintptr
// don't appear in the same expression
func illegalUseA() {
fmt.Println("===================== illegalUseA") pa := new([4]int) // split the legal use
// p1 := unsafe.Pointer(uintptr(unsafe.Pointer(pa)) + unsafe.Sizeof(pa[0]))
// into two expressions (illegal use):
ptr := uintptr(unsafe.Pointer(pa))
p1 := unsafe.Pointer(ptr + unsafe.Sizeof(pa[0]))
// "go vet" will make a warning for the above line:
// possible misuse of unsafe.Pointer // the unsafe package docs, https://golang.org/pkg/unsafe/#Pointer,
// thinks above splitting is illegal.
// but the current Go compiler and runtime (1.7.3) can't detect
// this illegal use.
// however, to make your program run well for later Go versions,
// it is best to comply with the unsafe package docs. *(*int)(p1) = 123
fmt.Println("*(*int)(p1) :", *(*int)(p1)) //
} // case B: pointers are pointing at unknown addresses
func illegalUseB() {
fmt.Println("===================== illegalUseB") a := [4]int{0, 1, 2, 3}
p := unsafe.Pointer(&a)
p = unsafe.Pointer(uintptr(p) + uintptr(len(a)) * unsafe.Sizeof(a[0]))
// now p is pointing at the end of the memory occupied by value a.
// up to now, although p is invalid, it is no problem.
// but it is illegal if we modify the value pointed by p
*(*int)(p) = 123
fmt.Println("*(*int)(p) :", *(*int)(p)) // 123 or not 123
// the current Go compiler/runtime (1.7.3) and "go vet"
// will not detect the illegal use here. // however, the current Go runtime (1.7.3) will
// detect the illegal use and panic for the below code.
p = unsafe.Pointer(&a)
for i := 0; i <= len(a); i++ {
*(*int)(p) = 123 // Go runtime (1.7.3) never panic here in the tests fmt.Println(i, ":", *(*int)(p))
// panic at the above line for the last iteration, when i==4.
// runtime error: invalid memory address or nil pointer dereference p = unsafe.Pointer(uintptr(p) + unsafe.Sizeof(a[0]))
}
} func main() {
illegalUseA()
illegalUseB()
}

编译器很难检测Go程序中非法的unsafe.Pointer使用。 运行“go vet”可以帮助找到一些潜在的错误,但不是所有的都能找到。 同样是Go运行时,也不能检测所有的非法使用。 非法unsafe.Pointer使用可能会使程序崩溃或表现得怪异(有时是正常的,有时是异常的)。 这就是为什么使用不安全的包是危险的。

转换T1 为 T2

对于将 T1转换为unsafe.Pointer,然后转换为 T2,unsafe包docs说:

如果T2比T1大,并且两者共享等效内存布局,则该转换允许将一种类型的数据重新解释为另一类型的数据。

这种“等效内存布局”的定义是有一些模糊的。 看起来go团队故意如此。 这使得使用unsafe包更危险。

由于Go团队不愿意在这里做出准确的定义,本文也不尝试这样做。 这里,列出了已确认的合法用例的一小部分,

合法用例1:在[]T和[]MyT之间转换

在这个例子里,我们用int作为T:

type MyInt int

在Golang中,[] int和[] MyInt是两种不同的类型,它们的底层类型是自身。 因此,[] int的值不能转换为[] MyInt,反之亦然。 但是在unsafe.Pointer的帮助下,转换是可能的:

package main

import (
"fmt"
"unsafe"
) func main() {
type MyInt int a := []MyInt{0, 1, 2}
// b := ([]int)(a) // error: cannot convert a (type []MyInt) to type []int
b := *(*[]int)(unsafe.Pointer(&a)) b[0]= 3 fmt.Println("a =", a) // a = [3 1 2]
fmt.Println("b =", b) // b = [3 1 2] a[2] = 9 fmt.Println("a =", a) // a = [3 1 9]
fmt.Println("b =", b) // b = [3 1 9]
}

合法用例2: 调用sync/atomic包中指针相关的函数

sync / atomic包中的以下函数的大多数参数和结果类型都是unsafe.Pointer或*unsafe.Pointer:

  • func CompareAndSwapPointer(addr * unsafe.Pointer,old,new unsafe.Pointer)(swapped bool)
  • func LoadPointer(addr * unsafe.Pointer)(val unsafe.Pointer)
  • func StorePointer(addr * unsafe.Pointer,val unsafe.Pointer)
  • func SwapPointer(addr * unsafe.Pointer,new unsafe.Pointer)(old unsafe.Pointer)

要使用这些功能,必须导入unsafe包。 注意: unsafe.Pointer是一般类型,因此 unsafe.Pointer的值可以转换为unsafe.Pointer,反之亦然。

package main

import (
"fmt"
"log"
"time"
"unsafe"
"sync/atomic"
"sync"
"math/rand"
) var data *string // get data atomically
func Data() string {
p := (*string)(atomic.LoadPointer(
(*unsafe.Pointer)(unsafe.Pointer(&data)),
))
if p == nil {
return ""
} else {
return *p
}
} // set data atomically
func SetData(d string) {
atomic.StorePointer(
(*unsafe.Pointer)(unsafe.Pointer(&data)),
unsafe.Pointer(&d),
)
} func main() {
var wg sync.WaitGroup
wg.Add(200) for range [100]struct{}{} {
go func() {
time.Sleep(time.Second * time.Duration(rand.Intn(1000)) / 1000) log.Println(Data())
wg.Done()
}()
} for i := range [100]struct{}{} {
go func(i int) {
time.Sleep(time.Second * time.Duration(rand.Intn(1000)) / 1000)
s := fmt.Sprint("#", i)
log.Println("====", s) SetData(s)
wg.Done()
}(i)
} wg.Wait() fmt.Println("final data = ", *data)
}

结论

  • unsafe包用于Go编译器,而不是Go运行时。
  • 使用unsafe作为程序包名称只是让你在使用此包是更加小心。
  • 使用unsafe.Pointer并不总是一个坏主意,有时我们必须使用它。
  • Golang的类型系统是为了安全和效率而设计的。 但是在Go类型系统中,安全性比效率更重要。 通常Go是高效的,但有时安全真的会导致Go程序效率低下。 unsafe包用于有经验的程序员通过安全地绕过Go类型系统的安全性来消除这些低效。
  • unsafe包可能被滥用并且是危险的。

原文:http://www.tapirgames.com/blog/golang-unsafe

go语言的unsafe包(转)的更多相关文章

  1. Go语言之unsafe包介绍及使用

    unsafe内容介绍 type ArbitraryType int type Pointer *ArbitraryType func Sizeof(x ArbitraryType) uintptr f ...

  2. unsafe包的学习和使用

    Go语言之unsafe包介绍及使用 unsafe内容介绍 type ArbitraryType int type Pointer *ArbitraryType func Sizeof(x Arbitr ...

  3. [转]Go里面的unsafe包详解

    Golang的unsafe包是一个很特殊的包. 为什么这样说呢? 本文将详细解释. 来自go语言官方文档的警告 unsafe包的文档是这么说的: 导入unsafe的软件包可能不可移植,并且不受Go 1 ...

  4. 深度解密Go语言之unsafe

    目录 指针类型 什么是 unsafe 为什么有 unsafe unsafe 实现原理 unsafe 如何使用 获取 slice 长度 获取 map 长度 map 源码中的应用 Offsetof 获取成 ...

  5. R语言︱H2o深度学习的一些R语言实践——H2o包

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- R语言H2o包的几个应用案例 笔者寄语:受启发 ...

  6. Go语言基础之包

    Go语言基础之包 在工程化的Go语言开发项目中,Go语言的源码复用是建立在包(package)基础之上的.本文介绍了Go语言中如何定义包.如何导出包的内容及如何导入其他包. Go语言的包(packag ...

  7. R语言:recommenderlab包的总结与应用案例

    R语言:recommenderlab包的总结与应用案例   1. 推荐系统:recommenderlab包整体思路 recommenderlab包提供了一个可以用评分数据和0-1数据来发展和测试推荐算 ...

  8. 使用R语言的RTCGA包获取TCGA数据--转载

    转载生信技能树 https://mp.weixin.qq.com/s/JB_329LCWqo5dY6MLawfEA TCGA数据源 - R包RTCGA的简单介绍 - 首先安装及加载包 - 指定任意基因 ...

  9. Java中的sun.misc.Unsafe包

    chronicle项目:https://github.com/peter-lawrey/Java-Chronicle 这个项目是利用mmap机制来实现高效的读写数据,号称每秒写入5到20百万条数据. ...

随机推荐

  1. SSH整合 第五篇 struts2的到来

    struts2的好处,web层的显示,同时Action类相当于MVC模式的C.整合进来的话,是通过与Spring整合,减少重复代码,利用IoC和AOP. 1.struts-2.5.2.jar 以上是s ...

  2. loadrunner提高篇 - 关联技术的经典使用

    关联函数是一个查找函数,即是从HTML文件内容中查找需要的值,并将其保存在一个变量或数组中.换一个角度看,关联函数不单单可以匹配一些变化的值,同样可以匹配一些固定的内容,并将其保存到一个数据组,供后续 ...

  3. JAVA中的内联函数

    在说内联函数之前,先说说函数的调用过程. 调用某个函数实际上将程序执行顺序转移到该函数所存放在内存中某个地址,将函数的程序内容执行完后,再返回到 转去执行该函数前的地方.这种转移操作要求在转去前要保护 ...

  4. CDialog类

    CDilalog包含三个关键函数:OnInitDialog.OnOK和OnCancel,可以覆盖这三个函数初始化对话框.响应点击OK和Cancel按钮.尽管每个函数都响应一条对话框消息,但是不需要你提 ...

  5. hdu 5039 线段树+dfs序

    http://acm.hdu.edu.cn/showproblem.php?pid=5039 给定一棵树,边权为0/1.m个操作支持翻转一条边的权值或者询问树上有多少条路径的边权和为奇数. 用树形df ...

  6. 查看Linux服务器被映射的公网ip

    查看Linux服务器被映射的公网ip   现在云服务器非常流行,不仅企业甚至是个人都可能拥有自己的云服务器,但是目前的云服务器厂商提供的公网IP大都是映射而来,所以在Linux服务器上执行ifconf ...

  7. 学习总结---INNODB 事务并发

    目前在做一个OLTP的数据库系统,批量读写和随机读写并发,情况比较复杂.INNODB是我们的MYSQL引擎,他的主要特点是读操作可以不受阻塞,而修改操作会加锁.如何才能最高效的使用innodb是我们需 ...

  8. Python 数据结构与算法——链表

    #构造节点类 class Node(object): def __init__(self,data=None,_next=None): ''' self.data:为自定义的数据 self.next: ...

  9. python中list的sort方法

    转:https://www.cnblogs.com/zle1992/p/6271105.html 使用python对列表(list)进行排序,说简单也简单,说复杂也复杂,我一开始学的时候也搞不懂在说什 ...

  10. Font Awesome矢量版,十六进制版,WPF字体使用

    我之前在博客中介绍过几个矢量图库网站,在WPF程序中,一般接触到的矢量图标资源有XAML.SVG.字体这三种格式.XAML是标准格式就不说了,SVG并不是直接支持的,不过微软提供了Expression ...