對於一個結構體,通過 offset 函數可以獲取結構體成員的偏移量,進而獲取成員的地址,讀寫該地址的內存,就可以達到改變成員值的目的。
這裏有一個內存分配相關的事實:結構體會被分配一塊連續的內存,結構體的地址也代表了第一個成員的地址。
我們來看一個例子:
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)
}
運行代碼,輸出:
{stefno go}
{qcrao Golang}
name 是結構體的第一個成員,因此可以直接將 &p 解析成 *string。這一點,在前面獲取 map 的 count 成員時,用的是同樣的原理。
對於結構體的私有成員,現在有辦法可以通過 unsafe.Pointer 改變它的值了。
我把 Programmer 結構體升級,多加一個字段:
type Programmer struct {
name string
age int
language string
}
並且放在其他包,這樣在 main 函數中,它的三個字段都是私有成員變量,不能直接修改。但我通過 unsafe.Sizeof() 函數可以獲取成員大小,進而計算出成員的地址,直接修改內存。
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)
}
輸出:
{stefno 18 go}
{stefno 18 Golang}