golang unsafe 包

閱讀原文

golang unsafe 包

ArbitraryType 和 Pointer

Go 語(yǔ)言是強(qiáng)類型語(yǔ)言,并且出于安全的考慮,它不允許不同類型的指針互相轉(zhuǎn)換,比如*int不能轉(zhuǎn)為*float64。但是它提供了 unsafe 包來(lái)做轉(zhuǎn)換。

type ArbitraryType int
type Pointer *ArbitraryType

從命名可以看出,ArbitraryType 代表了任意類型,其實(shí),ArbitraryType不是一個(gè)真正的類型,它只是一個(gè)占位符。而 Pointer 是其指針,并且是一種特殊意義的指針,它可以包含任意類型的地址,有點(diǎn)類似于 C 語(yǔ)言里的void* 指針,全能型的。

ArbitraryType 上有三個(gè)函數(shù):

func Alignof(variable ArbitraryType)uintptr
func Offsetof(selector ArbitraryType)uintptr
func Sizeof(variable ArbitraryType)uintptr

與Golang中的大多數(shù)函數(shù)不同,上述三個(gè)函數(shù)的調(diào)用將始終在編譯時(shí)求值,而不是運(yùn)行時(shí)。 這意味著它們的返回結(jié)果可以分配給常量。(BTW,unsafe包中的函數(shù)中非唯一調(diào)用將在編譯時(shí)求值。當(dāng)傳遞給len和cap的參數(shù)是一個(gè)數(shù)組值時(shí),內(nèi)置函數(shù)和cap函數(shù)的調(diào)用也可以在編譯時(shí)被求值。)

uintptr

uintptr 不是 unsafe 包的一部分,但是它總是和 unsafe 一起用。uintptr 是底層內(nèi)置類型,用于表示指針的值,區(qū)別在于go 語(yǔ)言中指針不可以參與計(jì)算,而 uintptr 可以。另外,指針和 uintptr 也是不可以直接轉(zhuǎn)換的。

特別需要注意的是,GC 不會(huì)把 uintptr 當(dāng)成指針,所以由 uintptr 變量表示的地址處的數(shù)據(jù)也可能被GC回收。

用法及注意事項(xiàng)

轉(zhuǎn)換不同類型的指針

func Float64bits(f float64) uint64 {
    return *(*uint64)(unsafe.Pointer(&f))
 }

把指針轉(zhuǎn)換成 uintptr

Converting a Pointer to a uintptr creates an integer value with no pointer semantics
//上面說(shuō)過的,uintptr 沒有指針的含義

如下轉(zhuǎn)換:

var a int64 = 0
pa := &a
up := uintptr(unsafe.Pointer(pa))
pa = &int64(1)

當(dāng) pa 地址改變,uintptr 是不會(huì)更新的。且當(dāng)只有 up 包含了變量 a 的地址,但是 GC 不會(huì)把 up 當(dāng)做指針,所以GC 會(huì)回收變量 a 。

uintptr 轉(zhuǎn)指針

p := &T{}
p = unsafe.Pointer(uintptr(p) + offset)

這里的 offset 得當(dāng)?shù)脑?,可以取?T 類型中沒有導(dǎo)出的值,這也是一個(gè)巧妙的用法,但是不推薦。注意這里不能寫成這樣:

p := &T{}   // 1
up := uintptr(p)    // 2
p = unsafe.Pointer(up + offset) //3

這樣非常危險(xiǎn),因?yàn)橛锌赡茉?3 執(zhí)行之前,up 這個(gè)臨時(shí)變量被 GC ,最終操作的不知道是哪個(gè)內(nèi)存了。因此不能將 uintptr(p) 保存在變量中。

另外,在 C語(yǔ)言 中我們可以將 offset 設(shè)成 T 的長(zhǎng)度,然后直接對(duì)得到的地址進(jìn)行操作。但是在 go 語(yǔ)言中是不合法的,可以讀取,但不應(yīng)該操作分配給 T 內(nèi)存之外的部分,會(huì)引發(fā) panic:

panic: runtime error: invalid memory address or nil pointer dereference

系統(tǒng)調(diào)用時(shí)轉(zhuǎn)換指針

syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))

如上,當(dāng)系統(tǒng)調(diào)用需要一個(gè) uintptr 作為參數(shù),也一定把 uintptr(..) 放在系統(tǒng)調(diào)用表達(dá)式的參數(shù)里,以防止被 GC。在系統(tǒng)調(diào)用過程中,不必?fù)?dān)心 uintptr 失效,它所持有的對(duì)象不會(huì)被 GC 。

reflect.Value.Pointer

在一些函數(shù)的返回值中,也可能出現(xiàn) uintptr,比如 reflect.Value.Pointerreflect.Value.UnsafeAddr,對(duì)其轉(zhuǎn)換成指針的時(shí)候也要注意,不能有中間變量:

p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))
//
// As in the cases above, it is invalid to store the result before the conversion:
//
//  INVALID: uintptr cannot be stored in variable
//  before conversion back to Pointer.
//  u := reflect.ValueOf(new(int)).Pointer()
//  p := (*int)(unsafe.Pointer(u))

Summary

  • unsafe包用于Go編譯器,而不是Go運(yùn)行時(shí)。
  • 使用unsafe作為程序包名稱只是讓你在使用此包是更加小心。
  • 使用unsafe.Pointer并不總是一個(gè)壞主意,有時(shí)我們必須使用它。
  • Golang的類型系統(tǒng)是為了安全和效率而設(shè)計(jì)的。 但是在Go類型系統(tǒng)中,安全性比效率更重要。 通常Go是高效的,但有時(shí)安全真的會(huì)導(dǎo)致Go程序效率低下。 unsafe包用于有經(jīng)驗(yàn)的程序員通過安全地繞過Go類型系統(tǒng)的安全性來(lái)消除這些低效。
  • unsafe包可能被濫用并且是危險(xiǎn)的
  • 涉及到 uintptr 轉(zhuǎn)指針時(shí),一定注意不能有中間變量

(續(xù))question

在關(guān)于操作不可知內(nèi)存的時(shí)候,會(huì)有一些莫名其妙的現(xiàn)象,如下代碼是 gocn 上一篇文章里的:

func main() {
    illegalUseB()
}

func illegalUseB() {
    a := [4]int{0, 1, 2, 3}
    p := unsafe.Pointer(&a)

    for i := 0; i <= len(a); i++ {
        *(*int)(p) = 1

        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) + 8)
    }
}

運(yùn)行這段代碼,報(bào)錯(cuò)如下:

0 : 1
1 : 1
2 : 1
3 : 1
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x1 pc=0x100ca32]
...

但是比較詭異的情況如下:

func illegalUseB() {
    a := [4]int{0, 1, 2, 3}
    p := unsafe.Pointer(&a)

    for i := 0; i <= len(a); i++ {
        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) + 8)
        *(*int)(p) = 1  // 調(diào)整了位置
    }
}

或者如下:

func illegalUseB() {

    a := [4]int{0, 1, 2, 3}
    p := unsafe.Pointer(&a)

    for i := 0; i <= len(a); i++ {
        *(*int)(p) = 1

        fmt.Println(i, ":", *(*int)(p), (*int)(p))  // 多輸出了一個(gè)值
        // 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) + 8)
    }
}

這兩種情況都不會(huì)報(bào)錯(cuò)。按理說(shuō)都是操作了聲明變量以外的內(nèi)存,但是沒有向之前一樣報(bào)錯(cuò),不知道是什么原因。我的 go SDK 版本是 1.9.1,如果你知道的話麻煩告訴我,謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • unsafe包提供了訪問底層內(nèi)存的方法。是用unsafe函數(shù)可以提高訪問對(duì)象的速度。通常用于對(duì)大數(shù)組的遍歷。 un...
    吃貓的魚0閱讀 2,333評(píng)論 0 3
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,051評(píng)論 0 9
  • Lua 5.1 參考手冊(cè) by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 14,246評(píng)論 0 38
  • 上次練得太猛了,這次在我的強(qiáng)烈央求下,改成了溫和版的。 1. 深蹲再舉能量棒。“你都求饒了,那就把12KG的換成1...
    MissHungry閱讀 164評(píng)論 0 1
  • 當(dāng)一首歌響起時(shí)會(huì)不會(huì)讓你突然想起一個(gè)人? ...
    _桃李不言_閱讀 676評(píng)論 0 3

友情鏈接更多精彩內(nèi)容