如何在golang中从不安全的数组创建数组或切片?

时间:2018-07-05 09:36:01

标签: arrays pointers go slice unsafe

一个指向数组的指针,比如说:

p := uintptr(unsafe.Pointer(&array))
size := 5

我无法访问变量array,上面的代码用于使其更加清晰。

此外,我知道数组的大小,但是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)

但是这种方法会进行内存复制吗(可能效率不高),是否仍然不进行复制?

P.S。我还尝试了以下两种方法,

// method 1
data := *(*[]byte)(unsafe.Pointer(p))
// method 2
data := *(*[size]byte)(unsafe.Pointer(p))

但是它将在运行时失败,我现在知道原因。

1 个答案:

答案 0 :(得分:9)

前言:

您应该知道:如果将指针作为uintptr类型的值来获取,则不会阻止原始数组被垃圾回收(uintptr值不作为引用)。因此,使用此类值时请务必小心,不能保证它会指向有效值/存储区。

从软件包unsafe.Pointer中报价:

  

uintptr是整数,而不是引用。将Pointer转换为uintptr会创建一个没有指针语义的整数值。即使uintptr保留了某个对象的地址,垃圾回收器也不会在对象移动时更新该uintptr的值,也不会阻止uintptr回收该对象。

一般建议:尽可能远离软件包unsafe。留在Go的类型安全中。


声明切片类型的变量,并使用不安全的转换来获取其reflect.SliceHeader描述符。

然后,您可以修改其字段,使用指针作为SliceHeader.Data的值,大小分别为SliceHeader.LenSliceHeader.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的地址并获得其长度之后,我们不再引用arrpuintptr不再作为引用),并且积极的GC可能会正确地擦除arr,然后我们才打印data(指向arr)。将runtime.KeepAlive()放在main()的末尾将确保不会arr被垃圾回收  在这个电话之前。有关详细信息,请参见In Go, when will a variable become unreachable?。如果指针的提供者确保不会对其进行垃圾收集,则无需在代码中调用runtime.KeepAlive()

或者,您可以使用composite literal创建一个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,以防止调用者将结果更改为任意类型,而无需首先导入“ unsafe”。但是,这意味着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