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. 几个CSS-content的小例子

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. jsp ckeditor ckfinder

    我认为对的事情,有可能是完全错的. 这篇教程很好: http://www.cnblogs.com/yuepeng/archive/2013/04/01/2992097.html 只看到9,权限控制还没 ...

  3. 磁盘配额(Quota)的应用与实践

    1>什么是Quota      在Linux中,由于是多用户,多任务的环境,所以会有多用户共同使用一个硬盘空间的情况发生,如果其中有少数几个用户大量占用掉了硬盘空间的话,那肯定影响其他用户的使 ...

  4. nginx backend 健康检查

    ngx_http_proxy_module 模块和ngx_http_upstream_module模块(自带) 严格来说,nginx自带是没有针对负载均衡后端节点的健康检查的,但是可以通过默认自带的n ...

  5. 前台ajax请求一次,后台代码执行了两次

    问题: 后台使用Django,前台发布文章时使用ajax方式,后台执行代码将data数据取出存入数据库 经过排查,是提交按钮type写成了submit,需要写成button,真是 修改后测试 代码执行 ...

  6. ASP.Net Core 2.2 MVC入门到基本使用系列 (一)

    本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...

  7. CentOS ASP.NET Core Runtime Jexus跨平台布署

    .net core 开源和跨平台,能布署到当前主流的Windows,Linux,macOS 系统上.本篇我们将在 Linux 系统上使用 ASP.NET Core Runtime 和 Jexus 布署 ...

  8. 20164317《网络对抗技术》Exp3 免杀原理与实践

    一.实验要求 1.1 正确使用msf编码器(0.5分),msfvenom生成如jar之类的其他文件(0.5分),veil-evasion(0.5分),加壳工具(0.5分),使用shellcode编程( ...

  9. hdoj1373 Channel Allocation(极大团)

    题意是有若干个接收器,给出每个接收器的相邻接收器.相邻的接收器不能使用同一信号频道.问所需要的信号频道数. 求该无向图的极大团. #include<iostream> #include&l ...

  10. win10无法访问samba共享

    地址: https://blog.csdn.net/xiaohuixing16134/article/details/79601064?utm_source=blogxgwz1 问题描述:配置好sam ...