1. 程式人生 > 其它 >unsafe 指標轉換與記憶體操作

unsafe 指標轉換與記憶體操作

Golang 提供了 unsafe 包,讓我們能夠直接操作指定記憶體地址的記憶體。

通過 unsafe.Pointer() 函式,我們能夠獲取變數的記憶體地址表示,本質上這是個整數。可以將任意變數的地址轉換成 Pointer 型別,也可以將 Pointer 型別轉換成任意的指標型別,它是不同指標型別之間互轉的中間型別。

Pointer 不支援運算,如果要在記憶體地址上進行加減運算,需要將其轉為 uintptr 型別。

下面我們嘗試讀取切片地址,並通過記憶體操作遍歷其內容:

package main

import "fmt"
import "unsafe"

func main() {
	// head = {address, 10, 10}
	// body = [1,2,3,4,5,6,7,8,9,10]
	var s = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	var address = (**[10]int)(unsafe.Pointer(&s))
	var len = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8)))
	var cap = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(16)))
	fmt.Println(address, *len, *cap)
	var body = **address
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", body[i])
	}
}
----------
0xc000004460 10 10
1 2 3 4 5 6 7 8 9 10 


上述程式碼中:

  • unsafe.Pointer(&s) 獲取切片 s 底層表示的第一個位置的記憶體地址,也即底層陣列的地址存放地址,
  • 通過 (**[10]int)(unsafe.Pointer(&s)) 將其轉為 **[10]int 型別指標,又通過 **addrss 還原為陣列;
  • unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8)) 通過地址運算,獲得 length 的存放地址,進而通過 (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8)))
    length 記憶體轉為 int 指標
    最後通過 *len 獲取切片長度;
    對於 cap 的操作與 len 類似,不再贅述;

總之:

  • 通過 unsafe,我們能夠實現記憶體地址在不同指標型別間的轉換,進而更靈活地操作記憶體;
  • 本實驗也進一步驗證了切片的底層儲存結構;
  • unsafe 在不是必須的條件下應該少使用,直接操作記憶體畢竟是風險較大的;