func main() {
s := make([]int, 10)
s[1] = 2
p := &s[0]
fmt.Println(*p)
up := uintptr(unsafe.Pointer(p))
up = unsafe.Sizeof(int(0))
p2 := (*int)(unsafe.Pointer(up))
fmt.Println(*p2)
}
输出:
0
2
从代码中我们可以看到,我们首先将指针指向切片的第一个位置,然后通过转换得到uintptr,操作uintptr 上8位(注意这里不能 因为存放的是int,下一个元素位置相隔举例int个字节),最后转换回来得到指针,取值,就能取到切片的第二个位置了。
unsafe操作struct(可以访问私有属性)
我们知道如果一个结构体里面定义的属性是私有的,那么这个属性是不能被外界访问到的。我们来看看下面这个操作:
package main
type User struct {
age int
name string
}
package main
func main() {
user := &User{}
fmt.Println(user)
s := (*int)(unsafe.Pointer(user))
*s = 15
up := uintptr(unsafe.Pointer(user)) unsafe.Sizeof(int(0))
namep := (*string)(unsafe.Pointer(up))
*namep = "ljy"
fmt.Println(user)
}
User是另外一个basic包中的结构体,其中的age是小写开头的,理论上来说,我们在外部没有办法修改age的值,但是经过上面这波操作之后,输出信息是:
&{0 }
&{10 xxx}
也就是说成功操作到了结构体的私有属性。
顺便提一句:创建结构体会被分配一块连续的内存,结构体的地址也代表了第一个成员的地址。
字符串和byte数组转换inplace
我们知道如果将字符串转换成[]byte非常方便
s := "123"
a := []byte(s)
但是这样需要开辟额外的空间,那么如何实现原地的,不需要拷贝数据的转换呢?
其实从底层的存储角度来说,string的存储规则和[]byte是一样的,也就是说,其实指针都是从某个位置开始到一段空间,中间一格一格。所以利用unsafe就可以做到。
func main() {
s := "123"
a := []byte(s)
print("s = " , &s, "\n")
print("a = " , &a, "\n")
a2 := (*[]byte)(unsafe.Pointer(&s))
print("a2 = " , a2, "\n")
fmt.Println(*a2)
}
输出结果:
s = 0xc420055f40
a = 0xc420055f60
a2 = 0xc420055f40
[49 50 51]
我们可以看到s和a的地址是不一样的,但是s和a2的地址是一样的,并且a2已经是一个[]byte了。
存在的问题
其实这个转换是存在问题的,问题就在新的[]byte的Cap没有正确的初始化。
我们打印一下cap看一下
fmt.Println(“cap a =”, cap(a))
fmt.Println(“cap a2 =”, cap(*a2))
结果是:
cap a = 32
cap a2 = 17418400
问题的原因
在src/reflect/value.go下看
type StringHeader struct {
Data uintptr
Len int
}
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
看到其实string没有cap而[]byte有,所以导致问题出现,也容易理解,string是没有容量扩容这个说法的,所以新的[]byte没有赋值cap所以使用了默认值。
问题解决
stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := reflect.SliceHeader{
Data: stringHeader.Data,
Len: stringHeader.Len,
Cap: stringHeader.Len,
}
return *(*[]byte)(unsafe.Pointer(&bh))
通过重新设置SliceHeader就可以完成