Go語言之unsafe

很多同學可能很奇怪,Go不是有指針類型了嗎?為啥還需要這樣一個unsafe的包呢,關鍵這個包的名字還是這麼的讓人不放心,叫做不安全包?而這個包又能夠做什麼呢,可以幫助我們解決那些問題呢?

筆者在學習Go的時候,也被這些問題困擾著,於是便自己整理了這篇文章來講解下,希望對大家有所幫助。

1.指針類型介紹:

對於Go語言的指針類型來說,指針類型的限制比較多,主要有四點,並且在Go語言的編譯階段就可以檢測出來。

限制1: 指針不可以進行數學計算。 限制2:不同類型的指針不能夠相互賦值。 限制3:不同類型的指針不能夠==或者!=的比較操作。 限制4:不同類型的指針不能夠相互轉化。

備註:Go語言又對類型要求的非常嚴格,就連type INT int中的int和INT都認為不是同一種類型,這就導致了指針的限制條件更加苛刻了。

1)指針變數正常使用的例子:

2)指針變數受限制的例子:

2. unsafe介紹

通過前面指針類型的介紹,我們能夠看出來unsafe之所以存在,就是因為Go語言對指針類型的限制太苛刻導致的。而unsafe的出現,恰恰彌補了指針變數的這些限制,不過從Go語言的設計者的角度來說,其實他們是不希望大家使用unsafe這個包的,不過有些場景又不得不用它,所以才特意起了個名字叫做unsafe的包,來使用它。

2.1 unsafe使用的場景

場景1:解決第三方庫提供的指針類型所使用的數據類型的不匹配問題。

場景2:解決數據結構體中,不可見屬性的取值和修改操作。

2.2 unsafe的特點

場景1:任何類型的指針均可以轉換成unsafe.Pointer,unsafe.Pointer也可以轉換成任何類型的指針,例子如下所示:

通過上面例子,以及輸出結果,我們可以看出,unsafe.Pointer可以將int類型的指針變數轉換成float32類型的指針變數。

場景2: uintptr 可以轉換為 Pointer,Pointer 也可以轉換為 uintptr,例子如下:

結果分析:通過上例的輸出結果,可以看出unsafe包是如何來操作,以及修改數據結構Node中不可見的元素age和name的。在修改Node中的第一個元素的時候,我們只需要取到該數據結構的存儲位置就可以;在修改後續的元素的時候,我們需要採用unsafe.Offsetof計算偏移的方式來找到對應的元素的存儲位置,然後再去修改這個數據。

2.3 uintptr不要賦值給臨時變數

從 GC 的角度來看,uintptr 類型的臨時變數只是一個無符號整數,並不知道它是一個指針地址,因此當滿足一定條件後,ptr 這個臨時變數是可能被垃圾回收掉的,所以我們不能夠將uintptr(nP)先賦值給一個臨時變數,在進行後面的取地址操作,程式碼如下所示:

結果分析:儘管運行結果依然是我們希望的,但是卻存在數據tmp被改掉的風險,所以要避免上面的使用方法。


參考文檔:

https://www.flysnow.org/2017/07/06/go-in-action-unsafe-pointer.html

https://golang.org/pkg/unsafe/