Go 通關:Go 指針和 unsafe-Pointer 有什麼區別?

指針類型轉換

在 Go 語言中,處於安全考慮,是不允許兩個指針類型進行轉換的,比如 *int 不能轉爲 *float64。

func main() {
    i := 5
    ip := &i
    var fp *float64 = (*float64)(ip)
}

運行結果:

cannot convert ip (type * int) to type * float64

發現報錯了,並不能進行強制轉型。
如果你非要~,那麼 Go 提供了 unsafe 包,使用包裏的 Pointer 來進行轉換!

unsafe.Pointer

unsafe.Pointer 是一種特殊意義的指針,可以表示任意類型的地址。使用它可以進行兩個指針類型的轉換。

func main() {
   i:= 5
   ip := &i
   var fp *float64 = (*float64)(unsafe.Pointer(ip))
   *fp = *fp * 6
   fmt.Println(i)
}

示例中我們通過 unsafe.Pointer 可以在不同指針類型之間做任何轉換。我們看下 unsafe.Pointer 的源碼定義:

// ArbitraryType is here for the purposes of documentation
// only and is not actually part of the unsafe package. 
// It represents the type of an arbitrary Go expression.
type ArbitraryType int
type Pointer *ArbitraryType
  • ArbitraryType 可以表示任何類型,ArbitraryType 我們無需關注太多,知道它可以表示任何類型即可

  • unsafe.Pointer 是 *ArbitraryType,表明 unsafe.Pointer 是任何類型的指針,是一個通用型的指針,可以表示任何內存地址。

uintptr 指針類型

uintptr 也是一種指針類型,也可以表示任何類型,和 unsafe.Pointer 的區別是,「uintptr 可以進行運算」。通過 uintptr 可以對指針偏移進行計算,來達到訪問特定的內存,對特定內存進行讀寫的目的。
示例:

func main() {
   p := new(person)
   //Name是person的第一個字段不用偏移,即可通過指針修改
   pName := (*string)(unsafe.Pointer(p))
   *pName = "微客鳥窩"
   //Age並不是person的第一個字段,所以需要進行偏移,這樣才能正確定位到Age字段這塊內存,纔可以正確的修改
   pAge := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(p))+unsafe.Offsetof(p.Age)))
   *pAge = 20
   fmt.Println(*p)
}
type person struct {
   Name string
   Age int
}
//運行結果:
{微客鳥窩 20}

解析:

  1. 先使用 new 函數聲明一個 *person 類型的指針變量 p。

  2. 然後把 *person 類型的指針變量 p 通過 unsafe.Pointer,轉換爲 *string 類型的指針變量 pName。

  3. 因爲 person 這個結構體的第一個字段就是 string 類型的 Name,所以 pName 這個指針就指向 Name 字段(偏移爲 0),對 pName 進行修改其實就是修改字段 Name 的值。

  4. 因爲 Age 字段不是 person 的第一個字段,要修改它必須要進行指針偏移運算。所以需要先把指針變量 p 通過 unsafe.Pointer 轉換爲 uintptr,這樣才能進行地址運算。具體偏移多少?可以通過函數 unsafe.Offsetof 計算出來,該函數返回的是一個 uintptr 類型的偏移量,有了這個偏移量就可以通過 + 號運算符獲得正確的 Age 字段的內存地址了,也就是通過 unsafe.Pointer 轉換後的 *int 類型的指針變量 pAge。

  5. 如果要進行指針運算,要先通過 unsafe.Pointer 轉換爲 uintptr 類型的指針。指針運算完畢後,還要通過 unsafe.Pointer 轉換爲真實的指針類型,這樣可以對這塊內存進行賦值或取值操作。

  6. 有了指向字段 Age 的指針變量 pAge,就可以對其進行賦值操作,修改字段 Age 的值了。

  7. 示例僅僅爲了演示 uintptr 的指針運算,正常編碼結構體賦值沒有這麼複雜:

   p :=new(person)
   p.Name = "微客鳥窩"
   p.Age = 20
   fmt.Println(*p)
}

指針轉換規則

三種指針類型:*T、unsafe.Pointer、unitptr

*T ←互轉→ unsafe.Pointer ←互轉→ unitptr

unsafe.Sizeof

Sizeof 函數可以返回一個類型所佔用的內存大小,這個大小隻與類型有關,和類型對應的變量存儲的數據大小無關,比如 bool 型佔用一個字節、int8 也佔用一個字節。
通過 Sizeof 函數可以查看任何類型佔用的內存大小,示例:

package main

import (
 "fmt"
 "unsafe"
)

func main() {
 fmt.Println(unsafe.Sizeof(true)) //1
 fmt.Println(unsafe.Sizeof(int8(0 //1
 fmt.Println(unsafe.Sizeof(int16(10))) //2
 fmt.Println(unsafe.Sizeof(int32(10000000))) //2
 fmt.Println(unsafe.Sizeof(int64(10000000000000))) //1
 fmt.Println(unsafe.Sizeof(int(10000000000000000)))
 fmt.Println(unsafe.Sizeof(string("微客鳥窩")))
 fmt.Println(unsafe.Sizeof([]string{"有碼無塵","無塵"}))
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/hiZyyLQM6ny9J6lggZffVA