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}
解析:
先使用 new 函數聲明一個 *person 類型的指針變量 p。
然後把 *person 類型的指針變量 p 通過 unsafe.Pointer,轉換爲 *string 類型的指針變量 pName。
因爲 person 這個結構體的第一個字段就是 string 類型的 Name,所以 pName 這個指針就指向 Name 字段(偏移爲 0),對 pName 進行修改其實就是修改字段 Name 的值。
因爲 Age 字段不是 person 的第一個字段,要修改它必須要進行指針偏移運算。所以需要先把指針變量 p 通過 unsafe.Pointer 轉換爲 uintptr,這樣才能進行地址運算。具體偏移多少?可以通過函數 unsafe.Offsetof 計算出來,該函數返回的是一個 uintptr 類型的偏移量,有了這個偏移量就可以通過 + 號運算符獲得正確的 Age 字段的內存地址了,也就是通過 unsafe.Pointer 轉換後的 *int 類型的指針變量 pAge。
如果要進行指針運算,要先通過 unsafe.Pointer 轉換爲 uintptr 類型的指針。指針運算完畢後,還要通過 unsafe.Pointer 轉換爲真實的指針類型,這樣可以對這塊內存進行賦值或取值操作。
有了指向字段 Age 的指針變量 pAge,就可以對其進行賦值操作,修改字段 Age 的值了。
示例僅僅爲了演示 uintptr 的指針運算,正常編碼結構體賦值沒有這麼複雜:
p :=new(person) p.Name = "微客鳥窩" p.Age = 20 fmt.Println(*p) }
指針轉換規則
三種指針類型:*T、unsafe.Pointer、unitptr
*T ←互轉→ unsafe.Pointer ←互轉→ unitptr
-
unsafe.Pointer 主要用於指針類型的轉換,是各個指針類型轉換的橋樑。
-
uintptr 主要用於指針運算,尤其是通過偏移量定位不同的內存。
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{"有碼無塵","無塵"}))
}
-
對於整型,佔用的字節數意味着這個類型存儲數字範圍的大小,比如 int8 佔用一個字節,也就是 8bit,所以它可以存儲的大小範圍是 -128~~127,也就是 −2^(n-1) 到 2^(n-1)−1。其中 n 表示 bit,int8 表示 8bit,int16 表示 16bit,以此類推。
-
對於和平臺有關的 int 類型,要看平臺是 32 位還是 64 位,會取最大的。
-
一個 struct 結構體的內存佔用大小,等於它包含的字段類型內存佔用大小之和。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/hiZyyLQM6ny9J6lggZffVA