?
This document uses PHP Chinese website manual Release
import "unsafe"
概觀
索引
軟件打包不安全包含圍繞 Go 程序類型安全的操作。
導(dǎo)入不安全的程序包可能不可移植,并且不受 Go 1 兼容性準則的保護。
func Alignof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Sizeof(x ArbitraryType) uintptr
type ArbitraryType
type Pointer
unsafe.go
func Alignof(x ArbitraryType) uintptr
Alignof 接受任何類型的表達式 x 并返回假設(shè)變量 v所需的對齊,就像 v 通過 var v = x 聲明一樣。它是最大的值 m,使得 v 的地址總是為零mod m。它與 reflect.TypeOf(x).Align() 返回的值相同。作為一種特殊情況,如果變量s是結(jié)構(gòu)類型,并且 f 是該結(jié)構(gòu)中的字段,那么 Alignof(s.f) 將返回結(jié)構(gòu)中該類型字段所需的對齊。這種情況與 reflect.TypeOf(s.f).FieldAlign() 返回的值相同。
func Offsetof(x ArbitraryType) uintptr
Offsetof 返回由 x 表示的字段結(jié)構(gòu)中的偏移量,它必須是 structValue.field 的形式。換句話說,它返回結(jié)構(gòu)開始和字段開始之間的字節(jié)數(shù)。
func Sizeof(x ArbitraryType) uintptr
Sizeof 采用任何類型的表達式x并返回假設(shè)變量 v 的字節(jié)大小,就像 v 通過 var v = x 聲明一樣。該大小不包括可能由 x 引用的任何內(nèi)存。例如,如果 x 是切片,則 Sizeof 返回切片描述符的大小,而不是切片引用的內(nèi)存大小。
ArbitraryType 僅用于文檔目的,實際上并不是不安全軟件包的一部分。它表示任意 Go 表達式的類型。
type ArbitraryType int
指針表示指向任意類型的指針。類型指針有四種可用于其他類型的特殊操作:
- A pointer value of any type can be converted to a Pointer.- A Pointer can be converted to a pointer value of any type.- A uintptr can be converted to a Pointer.- A Pointer can be converted to a uintptr.
指針因此允許程序打敗類型系統(tǒng)并讀寫任意內(nèi)存。應(yīng)該非常小心地使用它。
涉及指針的以下模式是有效的。不使用這些模式的代碼今天可能無效,或在將來變得無效。即使下面的有效模式也有重要的注意事項。
運行“go vet”可以幫助找到不符合這些模式的指針的使用,但是從“go vet”沉默并不能保證代碼有效。
(1)將 * T1 轉(zhuǎn)換為 * T2 的指針。
假設(shè) T2 不大于 T1 并且兩者共享相同的存儲器布局,則該轉(zhuǎn)換允許將一種類型的數(shù)據(jù)重新解釋為另一種類型的數(shù)據(jù)。一個例子是 math.Float64bits 的實現(xiàn):
func Float64bits(f float64) uint64 {return *(*uint64)(unsafe.Pointer(&f))}
(2)將指針轉(zhuǎn)換為 uintptr(但不返回到指針)。
將指針轉(zhuǎn)換為 uintptr 會以整數(shù)形式生成指向的內(nèi)存地址。這種 uintptr 通常用于打印它。
將 uintptr 轉(zhuǎn)換回指針通常無效。
uintptr 是一個整數(shù),而不是引用。將指針轉(zhuǎn)換為 uintptr 將創(chuàng)建不帶指針語義的整數(shù)值。即使 uintptr 持有某個對象的地址,如果對象移動,垃圾收集器也不會更新該 uintptr 的值,uintptr 也不會使該對象不被回收。
其余模式枚舉從 uintptr 到指針的唯一有效轉(zhuǎn)換。
(3)用算術(shù)將指針轉(zhuǎn)換為 uintptr 并返回。
如果 p 指向已分配的對象,則可以通過轉(zhuǎn)換為 uintptr,添加偏移量以及轉(zhuǎn)換回指針來將對象前進。
p = unsafe.Pointer(uintptr(p) + offset)
這種模式最常見的用途是訪問結(jié)構(gòu)中的字段或數(shù)組中的元素:
// equivalent to f := unsafe.Pointer(&s.f)f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))// equivalent to e := unsafe.Pointer(&x[i])e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))
以這種方式添加和減去指針上的偏移量是有效的。使用 &^ 來循環(huán)指針也是有效的,通常用于對齊。在所有情況下,結(jié)果必須繼續(xù)指向原始分配的對象。
與 C 中不同的是,僅僅在它的原始分配結(jié)束時超前一個指針是無效的:
// INVALID: end points outside allocated space.var s thing end = unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s))// INVALID: end points outside allocated space.b := make([]byte, n)end = unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))
請注意,這兩個轉(zhuǎn)換必須出現(xiàn)在相同的表達式中,只有它們之間的中間算術(shù):
// INVALID: uintptr cannot be stored in variable// before conversion back to Pointer.u := uintptr(p)p = unsafe.Pointer(u + offset)
(4)在調(diào)用 syscall.Syscall 時將指針轉(zhuǎn)換為 uintptr。
系統(tǒng)調(diào)用包中的 Syscall 函數(shù)直接將它們的 uintptr 參數(shù)傳遞給操作系統(tǒng),然后根據(jù)調(diào)用的細節(jié),將其中的一些重新解釋為指針。也就是說,系統(tǒng)調(diào)用實現(xiàn)隱式地將某些參數(shù)從 uintptr 轉(zhuǎn)換回指針。
如果必須將指針參數(shù)轉(zhuǎn)換為 uintptr 以用作參數(shù),則該轉(zhuǎn)換必須出現(xiàn)在調(diào)用表達式本身中:
syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))
編譯器處理一個轉(zhuǎn)換為 uintptr 的指針,轉(zhuǎn)換為在匯編中實現(xiàn)的函數(shù)的調(diào)用的參數(shù)列表中,通過安排被引用的已分配對象(如果有的話)被保留,直到調(diào)用完成時才移動,即使僅從類型在通話過程中將顯示該對象不再需要。
為了讓編譯器識別這種模式,轉(zhuǎn)換必須出現(xiàn)在參數(shù)列表中:
// INVALID: uintptr cannot be stored in variable// before implicit conversion back to Pointer during system call.u := uintptr(unsafe.Pointer(p))syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))
(5)將 reflect.Value.Pointer 或 reflect.Value.UnsafeAddr 的結(jié)果從 uintptr 轉(zhuǎn)換為指針。
Package 反射的 Value 方法名為 Pointer 和 UnsafeAddr,返回類型為 uintptr 而不是 unsafe.Pointer,以防止調(diào)用者將結(jié)果更改為任意類型,而不首先導(dǎo)入“unsafe”。但是,這意味著結(jié)果很脆弱,必須在調(diào)用后立即轉(zhuǎn)換為指針,并使用相同的表達式:
p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))
與上述情況一樣,在轉(zhuǎn)換之前存儲結(jié)果無效:
// INVALID: uintptr cannot be stored in variable// before conversion back to Pointer.u := reflect.ValueOf(new(int)).Pointer()p := (*int)(unsafe.Pointer(u))
(6)將 Reflection.SliceHeader 或 reflect.StringHeader 數(shù)據(jù)字段轉(zhuǎn)換為指針或從指針轉(zhuǎn)換而來。
與前面的情況一樣,反射數(shù)據(jù)結(jié)構(gòu) SliceHeader 和 StringHeader 將字段 Data 聲明為 uintptr,以防止調(diào)用者在不首先導(dǎo)入“不安全”的情況下將結(jié)果更改為任意類型。但是,這意味著 SliceHeader 和 StringHeader 僅在解釋實際切片或字符串值的內(nèi)容時有效。
var s string hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // case 1hdr.Data = uintptr(unsafe.Pointer(p)) // case 6 (this case)hdr.Len = n
在這種用法中,hdr.Data 實際上是一種替代方法來引用片頭中的基礎(chǔ)指針,而不是 uintptr 變量本身。
一般來說,reflect.SliceHeader 和 reflect.StringHeader 只能用作 * reflect.SliceHeader 和 * reflect.StringHeader 指向?qū)嶋H的切片或字符串,而不能用作普通結(jié)構(gòu)。程序不應(yīng)該聲明或分配這些結(jié)構(gòu)類型的變量。
// INVALID: a directly-declared header will not hold Data as a reference.var hdr reflect.StringHeader hdr.Data = uintptr(unsafe.Pointer(p))hdr.Len = n s := *(*string)(unsafe.Pointer(&hdr)) // p possibly already lost
type Pointer *ArbitraryType