unsafe 指標轉換與記憶體操作
阿新 • • 發佈:2022-05-10
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 在不是必須的條件下應該少使用,直接操作記憶體畢竟是風險較大的;