Golang 語言中的非類型安全指針

01

介紹

Golang 語言中的 unsafe 包中包含的操作會繞過 Golang 程序的類型安全檢查,直接操作內存,從而達到提升性能的目的。導入 unsafe 包可能是不可移植(non-portable)的(隨着 Golang 的版本迭代,可能會失效),並且不受 Go 1 兼容性準則的保護,所以我們應該謹慎使用。

本文主要介紹 unsafe 包的 unsafe.Pointer,它表示任意類型的指針,它類似於 C 語言中的無類型指針 void*,可以作爲指針類型 *T 和 uintptr 類型值之間互相轉換的中轉站。

我們知道 Golang 語言中的指針類型 *T,表示一個指向 T 類型變量的指針,因爲 Golang 語言是強類型的靜態語言,爲了安全考慮,規定兩個不同的指針類型之間不可以互相轉換,比如 *int 不能與 *float64 互相轉換。但是,實際上是可以使用 unsafe.Pointer 進行轉換。

Golang 語言中的內置數據類型 uintptr 也可以表示任何指針,它實際是數值類型,可以用於存儲內存地址。它和 unsafe.Pointer 最大的區別是 unsafe.Pointer 不支持指針運算,比如 + 運算符,但 uintptr 可以支持。以下是 uintptr 的源碼:

1// uintptr is an integer type that is large enough to hold the bit pattern of
2// any pointer.
3type uintptr uintptr
4
5

02

 unsafe.Ponter 類型

有了前面內容的鋪墊,我們開始介紹 unsafe.Ponter,它表示指向任意類型的指針。以下是 unsafe 包的源碼:

1// ArbitraryType is here for the purposes of documentation only and is not actually
2// part of the unsafe package. It represents the type of an arbitrary Go expression.
3type ArbitraryType int
4type Pointer *ArbitraryType
5
6

unsafe.Ponter 類型有四個轉換規則:

unsafe.Pointer 允許程序繞過類型安全檢查讀寫任意內存,所以使用時應格外小心。

unsafe.Pointer 包含 6 個使用模式:

03

 unsafe.Pointer 和 uintptr 使用示例

因爲 unsafe.Pointer 不支持運算,所以如果需要指針運算,還需要藉助 uintptr 實現。以下示例是通過指針偏移對 struct 結構體中的字段進行指針運算操作,從而找到該字段的內存地址。

 1package main
 2
 3import (
 4 "fmt"
 5 "unsafe"
 6)
 7
 8type user struct {
 9 name string
10 age uint
11}
12func main () {
13 // 定義一個指針變量
14 student := new(user)
15 // user 結構體中的 name 字段是第一個字段,可以直接通過指針修改,不需要使用偏移
16 studentName := (*string)(unsafe.Pointer(student))
17 *studentName = "lucy"
18 // user 結構體中的 age 字段不是第一個字段,所以需要使用偏移才能找到 age 字段的內存地址,修改值
19 studentAge := (*uint)(unsafe.Pointer(uintptr(unsafe.Pointer(student)) + unsafe.Offsetof(student.age)))
20 *studentAge = 18
21 fmt.Println(*student)
22}
23
24

閱讀上面這段代碼,我們使用 new 函數定義了一個 *user 類型的指針變量 student,然後使用 unsafe.Pointer 將 *user 類型的指針變量 student 轉換爲 *string 類型的指針變量 studentName,然後修改 studentName 的值,實際上就是修改 name 字段的值。

因爲 age 字段不是 user 結構體的第一個字段,所以需要先使用偏移量找到 age 字段的內存地址,具體操作步驟是:先將 student 指針變量通過 unsafe.Pointer 和 uintptr 轉換爲 uintptr,然後就可以使用函數 unsafe.Offsetof 計算出 age 字段的偏移量,該函數返回結果也是 uintptr 類型,因爲 uintptr 支持運算,最後使用 + 運算符獲取 age 字段的內存地址。

找到 age 字段的內存地址之後,還要使用 unsafe.Pointer 轉換爲 *uint 指針類型,纔可以對該內存地址進行讀寫操作。

該示例主要是爲了方便介紹 uintptr 指針運算,沒有實際意義。

04

總結

本文介紹了非類型安全指針,它可用於指針類型之間互相轉換,但是它繞開了類型安全檢查,同時隨着 Golang 的版本迭代,unsafe 包可能會失效,並且 unsafe 包不受 Go 1 兼容性準則的保護,所以我們應該謹慎使用。

參考資料:
https://golang.org/pkg/unsafe/#Pointer

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/MvULt7x0m4IBmz1bNzLvCQ