對於一個結構體,透過 offset 函式可以獲取結構體成員的偏移量,進而獲取成員的地址,讀寫該地址的記憶體,就可以達到改變成員值的目的。
這裡有一個記憶體分配相關的事實:結構體會被分配一塊連續的記憶體,結構體的地址也代表了第一個成員的地址。
我們來看一個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package main import ( "fmt" "unsafe" ) type Programmer struct { name string language string } func main() { p := Programmer{"stefno", "go"} fmt.Println(p) name := (*string)(unsafe.Pointer(&p)) *name = "qcrao" lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.language))) *lang = "Golang" fmt.Println(p) } |
執行程式碼,輸出:
1 2 |
{stefno go} {qcrao Golang} |
name 是結構體的第一個成員,因此可以直接將 &p 解析成 *string。這一點,在前面獲取 map 的 count 成員時,用的是同樣的原理。
對於結構體的私有成員,現在有辦法可以透過 unsafe.Pointer 改變它的值了。
我把 Programmer 結構體升級,多加一個欄位:
1 2 3 4 5 |
type Programmer struct { name string age int language string } |
並且放在其他包,這樣在 main 函式中,它的三個欄位都是私有成員變數,不能直接修改。但我透過 unsafe.Sizeof() 函式可以獲取成員大小,進而計算出成員的地址,直接修改記憶體。
1 2 3 4 5 6 7 8 9 |
func main() { p := Programmer{"stefno", 18, "go"} fmt.Println(p) lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Sizeof(int(0)) + unsafe.Sizeof(string("")))) *lang = "Golang" fmt.Println(p) } |
輸出:
1 2 |
{stefno 18 go} {stefno 18 Golang} |