unsafe 包的使用
go指针的限制
- go的指针不能进行数学运算
- 不同类型的指针不能相互转换
- 不同类型的指针不能使用 == 或 != 比较
- 不同类型的指针变量不能相互赋值
unsafe 包提供了 2 点重要的能力
- 任何类型的指针和 unsafe.Pointer 可以相互转换。
- uintptr 类型和 unsafe.Pointer 可以相互转换。
ps. uintptr 并没有指针的语义,意思就是 uintptr 所指向的对象会被 gc 无情地回收。而 unsafe.Pointer 有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收。
unsafe 包可以做什么坏事
1.绕过私有成员的限制
可以通过unsafe包绕过私有属性限制对私有属性读写。
package A
type A struct {
name string
age int
mark bool
}
package main
import (
"A"
"fmt"
"unsafe"
)
func main() {
a := A.A{}
fmt.Println(a)
name := (*string)(unsafe.Pointer(&p))
age := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Sizeof(string(""))))
mark := (*bool)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Sizeof(string("")) + unsafe.Sizeof(0)))
*name = "A"
*age = 20
*mark = false
// output {A 20 false}
fmt.Println(a)
}
2.通过伪造快速对私有属性更改
这边以内置类型slice为例,可以参考slice的源码。
ps. 与本主题无关的提示: make得到的slice是实体,make得到的map是指针。
package main
import (
"fmt"
"unsafe"
)
// 也可以使用小技巧,构造一个和 slice 一样的结构体来解析或操作切片
type slice struct {
arrptr unsafe.Pointer
l int
c int
}
func main() {
a := []int{1,2}
a = append(a, 3)
length := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&a)) + unsafe.Sizeof(unsafe.Pointer(&a))))
ca := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&a)) + unsafe.Sizeof(unsafe.Pointer(&a)) + unsafe.Sizeof(int(0))))
fmt.Println(*length, *ca)
*length = *length + 1 // + 1024 没问题, 只要在保护的内存段中就行
//a = append(a, 4)
fmt.Println(a, *length, *ca)
s := *(*slice)(unsafe.Pointer(&a))
fmt.Println(s)
fmt.Printf("[%d, %d, %d, %d]\n", *(*int)(s.arrptr), *(*int)(unsafe.Pointer(uintptr(s.arrptr) + unsafe.Sizeof(int(0)))),
*(*int)(unsafe.Pointer(uintptr(s.arrptr) + 2*unsafe.Sizeof(int(0)))),
*(*int)(unsafe.Pointer(uintptr(s.arrptr) + 3*unsafe.Sizeof(int(0)))),
)
}