go語言的unsafe包

來源:互聯網
上載者:User

標籤:off   log   版本   pointer   兩種   因此   unknown   float   nbsp   

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 mainimport (    "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值不能被取值 (Dereference)。
    • 如果unsafe.Pointer變數仍然有效,則由unsafe.Pointer變數表示的地址處的資料不會被GC回收。
    • unsafe.Pointer是一個通用的指標類型,就像* int等。

由於uintptr是一個整數類型,uintptr值可以進行算術運算。 所以通過使用uintptr和unsafe.Pointer,我們可以繞過限制,* T值不能在Golang中計算位移量:

package mainimport (    "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 mainimport (    "fmt"    "unsafe")// case A: conversions between unsafe.Pointer and uintptr //         don‘t appear in the same expressionfunc 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 addressesfunc 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 mainimport (    "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 mainimport (    "fmt"    "log"    "time"    "unsafe"    "sync/atomic"    "sync"    "math/rand")var data *string// get data atomicallyfunc Data() string {    p := (*string)(atomic.LoadPointer(            (*unsafe.Pointer)(unsafe.Pointer(&data)),        ))    if p == nil {        return ""    } else {        return *p    }}// set data atomicallyfunc 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包(轉)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.