指向数组的指针,比方说:
p := uintptr(unsafe.Pointer(&array)) size := 5
我无法访问该变量array,上面的代码用于使其更加清晰。
array
另外,我知道数组的大小,但是size不是恒定的,它会根据运行时而变化。
size
现在,我想使用已知的指针,大小以及数据类型初始化切片或数组。
我想出了以下代码:
data := make([]byte, size) stepSize := unsafe.Sizeof(data[0]) for i := 0; i < size; i++ { data[i] = *(*byte)(unsafe.Pointer(p)) p += stepSize } fmt.println(data)
但是这种方法是否可以进行内存复制(可能效率不高)?
PS我还尝试了以下两种方法,
// method 1 data := *(*[]byte)(unsafe.Pointer(p)) // method 2 data := *(*[size]byte)(unsafe.Pointer(p))
但它将在运行时失败,我现在知道其原因。
前言:
您应该知道:如果将指针作为uintptr类型的值获取,则不会阻止原始数组被垃圾回收(uintptr值不算作引用)。因此,使用此类值时请务必小心,不能保证它会指向有效值/存储区。
uintptr
从包装报价unsafe.Pointer:
unsafe.Pointer
uintptr是整数,而不是引用。将Pointer转换为uintptr会创建一个没有指针语义的整数值。即使uintptr拥有某个对象的地址,垃圾回收器也不会在对象移动时更新该uintptr的值,该uintptr也不会使该对象被回收。
一般建议:unsafe尽可能远离包装。留在Go的类型安全中。
unsafe
声明分片类型的变量,并使用不安全的转换来获取其 reflect.SliceHeader描述符。
reflect.SliceHeader
然后,您可以修改其字段,将指针用作SliceHeader.Data值,将大小用作SliceHeader.Len和SliceHeader.Cap。
SliceHeader.Data
SliceHeader.Len
SliceHeader.Cap
完成此操作后,slice变量将指向与初始指针相同的数组。
arr := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} size := len(arr) p := uintptr(unsafe.Pointer(&arr)) var data []byte sh := (*reflect.SliceHeader)(unsafe.Pointer(&data)) sh.Data = p sh.Len = size sh.Cap = size fmt.Println(data) runtime.KeepAlive(arr)
输出(在Go Playground上尝试):
[0 1 2 3 4 5 6 7 8 9]
请注意,我用过runtime.KeepAlive()。这是因为在获取地址arr并获取其长度之后,我们不再引用arr(p被uintptr当作参考),并且积极的GC可能会arr在我们准备打印之前data(正确地指向)删除。arr)。将a runtime.KeepAlive()放在的末尾main()将确保arr在此调用之前不会被垃圾回收。有关详细信息,请参阅InGo中的变量何时不可达?runtime.KeepAlive()如果指针的提供者确保不会被垃圾回收,则无需调用代码。
runtime.KeepAlive()
arr
p
data
main()
另外,您也可以创建reflect.SliceHeader带有复合文字的,并使用不安全的转换从中获取切片,如下所示:
sh := &reflect.SliceHeader{ Data: p, Len: size, Cap: size, } data := *(*[]byte)(unsafe.Pointer(sh)) fmt.Println(data) runtime.KeepAlive(arr)
输出将是相同的。在Go Playground上尝试一下。
在上记录了这种可能性/用例unsafe.Pointer,并附带警告和警告:
(6)将reflect.SliceHeader或reflect.StringHeader数据字段与指针进行转换。 与前面的情况一样,反射数据结构SliceHeader和StringHeader将字段Data声明为uintptr,以防止调用者在不首先导入“不安全”的情况下将结果更改为任意类型。但是,这意味着SliceHeader和StringHeader仅在解释实际切片或字符串值的内容时才有效。 var s string hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // case 1 hdr.Data = uintptr(unsafe.Pointer(p)) // case 6 (this case) hdr.Len = n 在这种用法中,hdr.Data实际上是引用切片头中基础指针的替代方法,而不是uintptr变量本身。 通常,reflect.SliceHeader和reflect.StringHeader只能用作指向实际切片或字符串的 reflect.SliceHeader和 reflect.StringHeader,而不能用作纯结构。程序不应声明或分配这些结构类型的变量。 // INVALID: a directly-declared header will not hold Data as a reference. var hdr reflect.StringHeader hdr.Data = uintptr(unsafe.Pointer(p)) hdr.Len = n s := (string)(unsafe.Pointer(&hdr)) // p possibly already lost
(6)将reflect.SliceHeader或reflect.StringHeader数据字段与指针进行转换。
与前面的情况一样,反射数据结构SliceHeader和StringHeader将字段Data声明为uintptr,以防止调用者在不首先导入“不安全”的情况下将结果更改为任意类型。但是,这意味着SliceHeader和StringHeader仅在解释实际切片或字符串值的内容时才有效。
var s string hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // case 1 hdr.Data = uintptr(unsafe.Pointer(p)) // case 6 (this case) hdr.Len = n
在这种用法中,hdr.Data实际上是引用切片头中基础指针的替代方法,而不是uintptr变量本身。
通常,reflect.SliceHeader和reflect.StringHeader只能用作指向实际切片或字符串的 reflect.SliceHeader和 reflect.StringHeader,而不能用作纯结构。程序不应声明或分配这些结构类型的变量。
// INVALID: a directly-declared header will not hold Data as a
reference. var hdr reflect.StringHeader hdr.Data = uintptr(unsafe.Pointer(p)) hdr.Len = n s := (string)(unsafe.Pointer(&hdr)) // p possibly already lost