如何从golang中的数组unsafe.Pointer创建数组或切片?

All*_* lu 6 arrays pointers unsafe go slice

一个指向数组的指针,让我们说:

p := uintptr(unsafe.Pointer(&array))
size := 5
Run Code Online (Sandbox Code Playgroud)

我无法访问变量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)
Run Code Online (Sandbox Code Playgroud)

但是这个方法会进行内存复制,这可能是效率低下的,无论如何都没有复制?

PS我也试过以下两种方法,

// method 1
data := *(*[]byte)(unsafe.Pointer(p))
// method 2
data := *(*[size]byte)(unsafe.Pointer(p))
Run Code Online (Sandbox Code Playgroud)

但它会在运行时失败,我现在知道它的原因.

icz*_*cza 12

前言:

您应该知道:如果将指针作为uintptr类型的值获取,则不会阻止原始数组进行垃圾回收(uintptr值不计入引用).所以在使用这样的值时要小心,不能保证它会指向有效的值/内存区域.

从包中引用unsafe.Pointer:

uintptr是整数,而不是引用.将指针转换为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)
Run Code Online (Sandbox Code Playgroud)

输出(在Go Playground上试试):

[0 1 2 3 4 5 6 7 8 9]
Run Code Online (Sandbox Code Playgroud)

请注意,我用过runtime.KeepAlive().这是因为走的地址后,arr并获得它的长度,我们不指arr了(p存在uintptr不计算为参考),并积极GC可能,理所当然地擦除arr之前我们得到的点打印data(指向arr).放置runtime.KeepAlive()到最后main()将确保arr在此调用之前不会收集垃圾.有关详细信息,请参阅In Go,变量何时无法访问?runtime.KeepAlive()如果指针的供应商确保它不会被垃圾收集,则无需调用代码.

或者,您可以reflect.SliceHeader使用复合文字创建一个,并使用不安全的转换从中获取切片,如下所示:

sh := &reflect.SliceHeader{
    Data: p,
    Len:  size,
    Cap:  size,
}

data := *(*[]byte)(unsafe.Pointer(sh))

fmt.Println(data)

runtime.KeepAlive(arr)
Run Code Online (Sandbox Code Playgroud)

输出将是相同的.在Go Playground尝试这个.

这个可能性/用例记录在案unsafe.Pointer,并附有警告和警告:

(6)将一个reflect.SliceHeader或reflect.StringHeader数据字段转换为Pointer或从指针转换.

与前一种情况一样,反射数据结构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
Run Code Online (Sandbox Code Playgroud)

在这种用法中,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
Run Code Online (Sandbox Code Playgroud)


Yen*_*ang 10

在 Go 1.17 中,我们现在有了unsafe.Slice(ptr *ArbitraryType, len IntegerType).

您可以使用此函数创建一个切片,其底层数组从 开始ptr,长度和容量为len

假设你有一个数组:

arr := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
Run Code Online (Sandbox Code Playgroud)

如果你有一个unsafe.Pointer名为p

p := unsafe.Pointer(&arr); // Or unsafe.Pointer(&arr[0])
data := unsafe.Slice((*byte)(p), len(arr)); 
Run Code Online (Sandbox Code Playgroud)

但如果您可以直接访问arr,则可以将其简化为:

data := unsafe.Slice(&arr[0], len(arr)); 
Run Code Online (Sandbox Code Playgroud)

警告:如果直接传递指针,请务必使用&arr[0]and not 。 是,不是。使用将创建长度和容量为 10 的 a,而不是长度和容量为 10 的 a。&arr&arr*[10]byte*byte&arr[][10]byte[]byte


在 Go 1.17 中,我们还可以方便地从切片转换为数组指针。简而言之,method 2如果您使用切片而不是unsafe.Pointer(p)

array := *(*[len(arr)]byte)(data)
Run Code Online (Sandbox Code Playgroud)