Go語言實戰筆記(二十七)| Go unsafe Pointer

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org或者網站http://www.flysnow.org/,第一時間看後續筆記。覺得有協助的話,順手分享到朋友圈吧,感謝支援。

Go語言在設計的時候,為了編寫方便、效率高以及降低複雜度,被設計成為一門強型別的靜態語言。強型別意味著一旦定義了,它的類型就不能改變了;靜態意味著類型檢查在運行前就做了。

同時為了安全的考慮,Go語言是允許兩個指標類型進行轉換的。

指標類型轉換

我們一般使用*T作為一個指標類型,表示一個指向類型T變數的指標。為了安全的考慮,兩個不同的指標類型不能相互轉換,比如*int不能轉為*float64

12345678
func main() {i:= 10ip:=&ivar fp *float64 = (*float64)(ip)fmt.Println(fp)}

以上代碼我們在編譯的時候,會提示cannot convert ip (type *int) to type *float64,也就是不能進行強制轉型。那如果我們還是需要進行轉換怎麼做呢?這就需要我們使用unsafe包裡的Pointer了,下面我們先看看unsafe.Pointer是什麼,然後再介紹如何轉換。

Pointer

unsafe.Pointer是一種特殊意義的指標,它可以包含任意類型的地址,有點類似於C語言裡的void*指標,全能型的。

12345678910
func main() {i:= 10ip:=&ivar fp *float64 = (*float64)(unsafe.Pointer(ip))*fp = *fp * 3fmt.Println(i)}

以上樣本,我們可以把*int轉為*float64,並且我們嘗試了對新的*float64進行操作,列印輸出i,就會發現i的址同樣被改變。

以上這個例子沒有任何實際的意義,但是我們說明了,通過unsafe.Pointer這個萬能的指標,我們可以在*T之間做任何轉換。

123
type ArbitraryType inttype Pointer *ArbitraryType

可以看到unsafe.Pointer其實就是一個*int,一個通用型的指標。

我們看下關於unsafe.Pointer的4個規則。

  1. 任何指標都可以轉換為unsafe.Pointer
  2. unsafe.Pointer可以轉換為任何指標
  3. uintptr可以轉換為unsafe.Pointer
  4. unsafe.Pointer可以轉換為uintptr

前面兩個規則我們剛剛已經示範了,主要用於*T1*T2之間的轉換,那麼最後兩個規則是做什麼的呢?我們都知道*T是不能計算位移量的,也不能進行計算,但是uintptr可以,所以我們可以把指標轉為uintptr再進行便宜計算,這樣我們就可以訪問特定的記憶體了,達到對不同的記憶體讀寫的目的。

下面我們以通過指標位移修改Struct結構體內的欄位為例,來示範uintptr的用法。

1234567891011121314151617
func main() {u:=new(user)fmt.Println(*u)pName:=(*string)(unsafe.Pointer(u))*pName="張三"pAge:=(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(u))+unsafe.Offsetof(u.age)))*pAge = 20fmt.Println(*u)}type user struct {name stringage int}

以上我們通過記憶體位移的方式,定位到我們需要操作的欄位,然後改變他們的值。

第一個修改username值的時候,因為name是第一個欄位,所以不用位移,我們擷取user的指標,然後通過unsafe.Pointer轉為*string進行賦值操作即可。

第二個修改userage值的時候,因為age不是第一個欄位,所以我們需要記憶體位移,記憶體位移牽涉到的計算只能通過uintptr,所我們要先把user的指標地址轉為uintptr,然後我們再通過unsafe.Offsetof(u.age)擷取需要位移的值,進行地址運算(+)位移即可。

現在位移後,地址已經是userage欄位了,如果要給它賦值,我們需要把uintptr轉為*int才可以。所以我們通過把uintptr轉為unsafe.Pointer,再轉為*int就可以操作了。

這裡我們可以看到,我們第二個位移的運算式非常長,但是也千萬不要把他們分段,不能像下面這樣。

123
temp:=uintptr(unsafe.Pointer(u))+unsafe.Offsetof(u.age)pAge:=(*int)(unsafe.Pointer(temp))*pAge = 20

邏輯上看,以上代碼不會有什麼問題,但是這裡會牽涉到GC,如果我們的這些臨時變數被GC,那麼導致的記憶體操作就錯了,我們最終操作的,就不知道是哪塊記憶體了,會引起莫名其妙的問題。

小結

unsafe是不安全的,所以我們應該儘可能少的使用它,比如記憶體的操縱,這是繞過Go本身設計的安全機制的,不當的操作,可能會破壞一塊記憶體,而且這種問題非常不好定位。

當然必須的時候我們可以使用它,比如底層類型相同的數組之間的轉換;比如使用sync/atomic包中的一些函數時;還有訪問Struct的私人欄位時;該用還是要用,不過一定要慎之又慎。

還有,整個unsafe包都是用於Go編譯器的,不用運行時,在我們編譯的時候,Go編譯器已經把他們都處理了。

《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org或者網站http://www.flysnow.org/,第一時間看後續筆記。覺得有協助的話,順手分享到朋友圈吧,感謝支援。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.