Golang的unsafe包是一个很特殊的包。 为何这样说呢? 本文将详细解释。程序员
unsafe包的文档是这么说的:golang
导入unsafe的软件包可能不可移植,而且不受Go 1兼容性指南的保护。
Go 1 兼容性指南这么说:express
导入unsafe软件包可能取决于Go实现的内部属性。 咱们保留对可能致使程序崩溃的实现进行更改的权利。
固然包名称暗示unsafe包是不安全的。 但这个包有多危险呢? 让咱们先看看unsafe包的做用。数组
直到如今(Go1.7),unsafe包含如下资源:安全
三个函数:app
和一种类型:函数
这里,ArbitraryType不是一个真正的类型,它只是一个占位符。布局
与Golang中的大多数函数不一样,上述三个函数的调用将始终在编译时求值,而不是运行时。 这意味着它们的返回结果能够分配给常量。ui
(BTW,unsafe包中的函数中非惟一调用将在编译时求值。当传递给len和cap的参数是一个数组值时,内置函数和cap函数的调用也能够在编译时被求值。)this
除了这三个函数和一个类型外,指针在unsafe包也为编译器服务。
出于安全缘由,Golang不容许如下之间的直接转换:
两个不一样指针类型的值,例如 int64和 float64。
可是借助unsafe.Pointer,咱们能够打破Go类型和内存安全性,并使上面的转换成为可能。这怎么可能发生?让咱们阅读unsafe包文档中列出的规则:
这些规则与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的事实:
因为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包,Ian,Go团队的核心成员之一,已经确认:
在unsafe包中的函数的签名将不会在之后的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转换为unsafe.Pointer,而后转换为 T2,unsafe包docs说:
若是T2比T1大,而且二者共享等效内存布局,则该转换容许将一种类型的数据从新解释为另外一类型的数据。
这种“等效内存布局”的定义是有一些模糊的。 看起来go团队故意如此。 这使得使用unsafe包更危险。
因为Go团队不肯意在这里作出准确的定义,本文也不尝试这样作。 这里,列出了已确认的合法用例的一小部分,
在这个例子里,咱们用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] }
sync / atomic包中的如下函数的大多数参数和结果类型都是unsafe.Pointer或*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) }