如何利用 unsafe 包修改私有成员
对于一个结构体,通过 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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
运行代码,输出:
{stefno go}
{qcrao Golang}
1
2
2
字段 name 是结构体的第一个成员, 因此可以直接将 &p 解析成 *string
, 接着直接修改 name 的值。有了第一个字段的地址后,可以通过 offset 求出第二个字段的地址:
lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.language)))
1
先将 unsafe.Pointer 转成 uintptr,然后进行数学计算,再转换为 unsafe.Pointer,最后再转成字符串指针。
对于结构体的私有成员,现在有办法可以通过 unsafe.Pointer 改变它的值了。把 Programmer 结构体升级,多加一个字段:
type Programmer struct {
name string
age int
language string
}
1
2
3
4
5
2
3
4
5
并且放在其他包里,这样在 main 函数中,它的三个字段都是私有成员变量,不能直接修改, 也不能对字段取 offset。但可以通过 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)
}
1
2
3
4
5
6
7
2
3
4
5
6
7
输出如下:
{stefno 18 go}
{stefno 18 Golang}
1
2
2
上次更新: 7/12/2024, 2:37:05 PM