unsafe 是不安全的,所以我们应该尽可能少的使用它,比如内存的操纵,这是绕过 Go 本身设计的安全机制的,不当的操作,可能会破坏一块内存,而且这种问题非常不好定位。
当然必须的时候我们可以使用它,比如底层类型相同的数组之间的转换;比如使用 sync/atomic 包中的一些函数时;还有访问 struct 的私有字段时;该用还是要用,不过一定要慎之又慎。
官方文档对unsafe.Pointer的描述的四个规则:
(1)任何类型的指针都可以被转化为Pointer
(2)Pointer可以被转化为任何类型的指针
(3)uintptr可以被转化为Pointer
(4)Pointer可以被转化为uintptr
Go 语言在设计的时候,为了编写方便、效率高以及降低复杂度,被设计成为一门强类型的静态语言。强类型意味着一旦定义了,它的类型就不能改变了;静态意味着类型检查在运行前就做了。
同时为了安全的考虑,Go 语言是不允许两个指针类型进行转换的。
一般使用 *T 作为一个指针类型,表示一个指向类型T变量的指针。为了安全的考虑,两个不同的指针类型不能相互转换,比如 *int 不能转为 *float64
import "fmt"
func main() {
i := 10
ip := &i
var fp *float64 = (*float64)(ip)
fmt.Println(fp)
}
以上代码我们在编译的时候,会提示 cannot convert ip (type *int) to type *float64,也就是不能进行强制转型
接以上,
func main() {
i := 10
ip := &i
var fp *float64 = (*float64)(unsafe.Pointer(ip))
*fp = *fp * 3
fmt.Println(i)
}
以上示例,我们可以把 *int 转为 *float64,并且我们尝试了对新的 *float64 进行操作,打印输出 i,就会发现 i 的值同样被改变。
的
前面两个规则我们刚刚已经演示了,主要用于 *T1 和 *T2 之间的转换,那么最后两个规则是做什么的呢?*我们都知道 T 是不能计算偏移量的,也不能进行计算,但是 uintptr 可以,所以我们可以把指针转为 uintptr 再进行偏移计算,这样我们就可以访问特定的内存了,达到对不同的内存读写的目的。
下面我们以通过指针偏移修改Struct结构体内的字段为例,来演示 uintptr 的用法
func main() {
u := new(user)
fmt.Println(*u)
pName := (*string)(unsafe.Pointer(u))
*pName = "张三"
pAge := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(u)) + unsafe.Offsetof(u.age)))
*pAge = 20
fmt.Println(*u)
}
type user struct {
name string
age int
}
以上我们通过内存偏移的方式,定位到我们需要操作的字段,然后改变他们的值。
第一个修改 user 的 name 值的时候,因为 name 是第一个字段,所以不用偏移,我们获取 user 的指针,然后通过 unsafe.Pointer 转为 *string 进行赋值操作即可。
第二个修改 user 的 age 值的时候,因为 age 不是第一个字段,所以我们需要内存偏移,内存偏移牵涉到的计算只能通过 uintptr,所我们要先把 user 的指针地址转为 uintptr,然后我们再通过 unsafe.Offsetof(u.age) 获取需要偏移的值,进行地址运算(+)偏移即可。
现在偏移后,地址已经是 user 的 age 字段了,如果要给它赋值,我们需要把 uintptr 转为 *int 才可以。所以我们通过把 uintptr 转为 unsafe.Pointer,再转为 *int 就可以操作了。
这里我们可以看到,我们第二个偏移的表达式非常长,但是也千万不要把他们分段,不能像下面这样。
temp := uintptr(unsafe.Pointer(u)) + unsafe.Offsetof(u.age)
pAge := (*int)(unsafe.Pointer(temp))
*pAge = 20
逻辑上看,以上代码不会有什么问题,但是这里会牵涉到 GC,如果我们的这些临时变量被 GC,那么导致的内存操作就错了,我们最终操作的,就不知道是哪块内存了,会引起莫名其妙的问题。
func main() {
v1 := uint(12)
v2 := int(13)
fmt.Println(reflect.TypeOf(v1)) //uint
fmt.Println(reflect.TypeOf(v2)) //int
fmt.Println(reflect.TypeOf(&v1)) //*uint
fmt.Println(reflect.TypeOf(&v2)) //*int
p := &v1
p = (*uint)(unsafe.Pointer(&v2)) //使用unsafe.Pointer进行类型的转换
fmt.Println(reflect.TypeOf(p)) // *unit
fmt.Println(*p) //13
}