回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,8 +1,8 @@
## 13.2. unsafe.Pointer
大多數指針類型會寫`*T`,表示是“一指向T類型變量的指”。unsafe.Pointer是特别定的一種指針類型(譯註:類似C言中的`void*`型的指),它可以包含任意類型變量的地址。然,我不可以直接通`*p`來獲取unsafe.Pointer指指向的眞實變量的值,因爲我們併不知道量的具體類型。和普通指針一樣unsafe.Pointer指也是可以比的,且支持和nil常量比較判斷是否空指
大多数指针类型会写`*T`,表示是“一指向T类型变量的指”。unsafe.Pointer是特别定的一种指针类型(译注:类似C言中的`void*`型的指),它可以包含任意类型变量的地址。然,我不可以直接通`*p`来获取unsafe.Pointer指指向的真实变量的值,因为我们并不知道量的具体类型。和普通指针一样unsafe.Pointer指也是可以比的,且支持和nil常量比较判断是否空指
普通的`*T`型指可以被轉化爲unsafe.Pointer型指針,併且一unsafe.Pointer型指也可以被轉迴普通的指,被轉迴普通的指針類型併不需要和原始的`*T`型相同。通過將`*float64`型指針轉化爲`*uint64`型指,我可以看一個浮點數變量的位模式。
普通的`*T`型指可以被转化为unsafe.Pointer型指针,并且一unsafe.Pointer型指也可以被转回普通的指,被转回普通的指针类型并不需要和原始的`*T`型相同。通过将`*float64`型指针转化为`*uint64`型指,我可以看一个浮点数变量的位模式。
```Go
package math
@@ -12,11 +12,11 @@ func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
```
過轉爲新類型指,我可以更新浮點數的位模式。通位模式操作浮點數是可以的,但是更重要的意是指針轉換語法讓我們可以在不破壞類型繫統的前提下向內存寫入任意的值。
过转为新类型指,我可以更新浮点数的位模式。通位模式操作浮点数是可以的,但是更重要的意是指针转换语法让我们可以在不破坏类型系统的前提下向内存写入任意的值。
unsafe.Pointer指也可以被轉化爲uintptr型,然保存到指針型數值變量中(譯註:這隻是和前指相同的一個數字值,不是一個指針),然用以做必要的指針數值運算。(第三章uintptr是一個無符號的整型,足以保存一地址)這種轉換雖然也是可逆的,但是uintptr轉爲unsafe.Pointer指可能會破壞類型繫統,因爲併不是所有的字都是有效的存地址。
unsafe.Pointer指也可以被转化为uintptr型,然保存到指针型数值变量中(译注:这只是和前指相同的一个数字值,不是一个指针),然用以做必要的指针数值运算。(第三章uintptr是一个无符号的整型,足以保存一地址)这种转换虽然也是可逆的,但是uintptr转为unsafe.Pointer指可能会破坏类型系统,因为并不是所有的字都是有效的存地址。
許多將unsafe.Pointer指針轉爲原生字,然後再轉迴爲unsafe.Pointer型指的操作也是不安全的。比如下面的例子需要將變量x的地址加上b字段地址偏移量轉化爲`*int16`型指,然後通過該指針更新x.b
许多将unsafe.Pointer指针转为原生字,然后再转回为unsafe.Pointer型指的操作也是不安全的。比如下面的例子需要将变量x的地址加上b字段地址偏移量转化为`*int16`型指,然后通过该指针更新x.b
<u><i>gopl.io/ch13/unsafeptr</i></u>
```Go
@@ -26,14 +26,14 @@ var x struct {
c []int
}
// 和 pb := &x.b 等
// 和 pb := &x.b 等
pb := (*int16)(unsafe.Pointer(
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 42
fmt.Println(x.b) // "42"
```
上面的寫法盡管很繁,但在這里併不是一件事,因爲這些功能應該很謹慎地使用。不要試圖引入一uintptr型的臨時變量,因它可能會破壞代碼的安全性(譯註:這是眞正可以體會unsafe包何不安全的例子)。下面段代碼是錯誤的:
上面的写法尽管很繁,但在这里并不是一件事,因为这些功能应该很谨慎地使用。不要试图引入一uintptr型的临时变量,因它可能会破坏代码的安全性(译注:这是真正可以体会unsafe包何不安全的例子)。下面段代码是错误的:
```Go
// NOTE: subtly incorrect!
@@ -42,21 +42,21 @@ pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42
```
産生錯誤的原因很微妙。有候垃圾收器會移動一些量以降低存碎片等問題。這類垃圾收器被稱爲移動GC。當一個變量被移,所有的保存改變量舊地址的指針必須同時被更新爲變量移動後的新地址。垃圾收集器的視角來看,一unsafe.Pointer是一指向量的指,因此當變量被移動是對應的指也必被更新但是uintptr型的臨時變量隻是一普通的字,所以其值不應該被改。上面錯誤的代碼因爲引入一非指針的臨時變量tmp致垃圾收集器法正確識别這個是一指向量x的指針。當第二個語句執行時變量x可能已經被轉移這時候臨時變量tmp也就不再是在的`&x.b`地址。第三向之前效地址空間的賦值語句將徹底摧譭整個程序!
产生错误的原因很微妙。有候垃圾收器会移动一些量以降低存碎片等问题。这类垃圾收器被称为移动GC。当一个变量被移,所有的保存改变量旧地址的指针必须同时被更新为变量移动后的新地址。垃圾收集器的视角来看,一unsafe.Pointer是一指向量的指,因此当变量被移动是对应的指也必被更新但是uintptr型的临时变量只是一普通的字,所以其值不应该被改。上面错误的代码因为引入一非指针的临时变量tmp致垃圾收集器法正确识别这个是一指向量x的指针。当第二个语句执行时变量x可能已经被转移这时候临时变量tmp也就不再是在的`&x.b`地址。第三向之前效地址空间的赋值语句将彻底摧毁整个程序!
有很多似原因致的錯誤。例如這條語句:
有很多似原因致的错误。例如这条语句:
```Go
pT := uintptr(unsafe.Pointer(new(T))) // 提示: 錯誤!
pT := uintptr(unsafe.Pointer(new(T))) // 提示: 错误!
```
這里併沒有指引用`new`建的量,因此該語句執行完成之,垃圾收集器有權馬上迴收其存空,所以返的pT將是無效的地址。
这里并没有指引用`new`建的量,因此该语句执行完成之,垃圾收集器有权马上回收其存空,所以返的pT将是无效的地址。
然目前的Go語言實現還沒有使用移GC譯註:未可能實現),但這不該是編寫錯誤代碼僥幸的理由:前的Go語言實現已經有移動變量的景。在5.2節我們提到goroutine的是根需要動態增長的。當發送棧動態增長的時候,原來棧中的所以量可能需要被移到新的更大的中,所以我們併不能確保變量的地址在整使用週期內是不的。
然目前的Go语言实现还没有使用移GC译注:未可能实现),但这不该是编写错误代码侥幸的理由:前的Go语言实现已经有移动变量的景。在5.2节我们提到goroutine的是根需要动态增长的。当发送栈动态增长的时候,原来栈中的所以量可能需要被移到新的更大的中,所以我们并不能确保变量的地址在整使用周期内是不的。
編寫本文時,還沒有清晰的原則來指引Go程序,什麽樣的unsafe.Pointer和uintptr的轉換是不安全的(考 [Issue7192](https://github.com/golang/go/issues/7192) . 譯註: 該問題已經關閉),因此我們強烈建按照最的方式理。所有包含量地址的uintptr類型變量當作BUG理,同時減少不必要的unsafe.Pointer型到uintptr型的轉換。在第一例子中,有三個轉換——字段偏移量到uintptr的轉換和轉迴unsafe.Pointer型的操作——所有的轉換全在一個表達式完成。
编写本文时,还没有清晰的原则来指引Go程序,什么样的unsafe.Pointer和uintptr的转换是不安全的(考 [Issue7192](https://github.com/golang/go/issues/7192) . 译注: 该问题已经关闭),因此我们强烈建按照最的方式理。所有包含量地址的uintptr类型变量当作BUG理,同时减少不必要的unsafe.Pointer型到uintptr型的转换。在第一例子中,有三个转换——字段偏移量到uintptr的转换和转回unsafe.Pointer型的操作——所有的转换全在一个表达式完成。
當調用一個庫函數,併且返的是uintptr型地址時(譯註:普通方法實現的函數不盡量不要返迴該類型。下面例子是reflect包的函reflect包和unsafe包一都是采用特殊技術實現的,編譯器可能給它們開了後門),比如下面反射包中的相關函數,返迴的結果應該立卽轉換爲unsafe.Pointer以保指指向的是相同的量。
当调用一个库函数,并且返的是uintptr型地址时(译注:普通方法实现的函数不尽量不要返回该类型。下面例子是reflect包的函reflect包和unsafe包一都是采用特殊技术实现的,编译器可能给它们开了后门),比如下面反射包中的相关函数,返回的结果应该立即转换为unsafe.Pointer以保指指向的是相同的量。
```Go
package reflect