对于一个结构体,通过 offset 函数可以获取结构体成员的偏移量,进而获取成员的地址,读写该地址的内存,就可以达到改变成员值的目的。

    这里有一个内存分配相关的事实:结构体会被分配一块连续的内存,结构体的地址也代表了第一个成员的地址。

    我们来看一个例子:

    1. package main
    2. import (
    3. "fmt"
    4. "unsafe"
    5. )
    6. type Programmer struct {
    7. name string
    8. language string
    9. }
    10. func main() {
    11. p := Programmer{"stefno", "go"}
    12. fmt.Println(p)
    13. name := (*string)(unsafe.Pointer(&p))
    14. *name = "qcrao"
    15. lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.language)))
    16. *lang = "Golang"
    17. fmt.Println(p)
    18. }

    运行代码,输出:

    1. {stefno go}
    2. {qcrao Golang}

    name 是结构体的第一个成员,因此可以直接将 &p 解析成 *string。这一点,在前面获取 map 的 count 成员时,用的是同样的原理。

    对于结构体的私有成员,现在有办法可以通过 unsafe.Pointer 改变它的值了。

    我把 Programmer 结构体升级,多加一个字段:

    1. type Programmer struct {
    2. name string
    3. age int
    4. language string
    5. }

    并且放在其他包,这样在 main 函数中,它的三个字段都是私有成员变量,不能直接修改。但我通过 unsafe.Sizeof() 函数可以获取成员大小,进而计算出成员的地址,直接修改内存。

    1. func main() {
    2. p := Programmer{"stefno", 18, "go"}
    3. fmt.Println(p)
    4. lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Sizeof(int(0)) + unsafe.Sizeof(string(""))))
    5. *lang = "Golang"
    6. fmt.Println(p)
    7. }

    输出:

    1. {stefno 18 go}
    2. {stefno 18 Golang}