如何使用golang中的unsafe.Pointer指针从数组创建数组或切片? [英] How to create an array or a slice from an array unsafe.Pointer in golang?

查看:164
本文介绍了如何使用golang中的unsafe.Pointer指针从数组创建数组或切片?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

A pointer to an array, let's say:

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

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

I can't access to the variable array, the code above is used to make it more clear.

此外,我知道数组的大小,但是size不是恒定的,它会根据运行时而变化.

Also, I know the size of the array, but the size is not constant, it changes according to the runtime.

现在,我想使用已知的指针,大小和数据类型初始化切片或数组.

Now, I want to initialize a slice or an array with the known pointer, size and of course the data type.

我想出了以下代码:

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)

但是这种方法会进行内存复制吗,可能效率很低,是否不进行复制就存在吗?

but this method does memory copy, which might be inefficient, is there anyway without doing the copy?

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

P.S. I also tried the following two methods,

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

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

but it will fail at runtime and I know its reason now.

推荐答案

前言:

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

You should know: if you get the pointer as a value of uintptr type, that does not prevent the original array to get garbage collected (an uintptr value does not count as a reference). So be careful when using such value, there is no guarantee it will point to a valid value / memory area.

从软件包 unsafe.Pointer 中引用:

Quoting from package unsafe.Pointer:

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

A uintptr is an integer, not a reference. Converting a Pointer to a uintptr creates an integer value with no pointer semantics. Even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed.

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

General advice: stay away from package unsafe as much as possible. Stay inside Go's type safety.

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

Declare a variable of slice type, and use unsafe conversions to obtain its reflect.SliceHeader descriptor.

然后,您可以修改其字段,将指针用作SliceHeader.Data值,并将大小设置为SliceHeader.LenSliceHeader.Cap.

Then you can modify its fields, using the pointer as the SliceHeader.Data value, and the size as SliceHeader.Len and SliceHeader.Cap.

完成此操作后,slice变量将指向与初始指针相同的数组.

Once you're done with this, the slice variable will point to the same array as your initial pointer.

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)

输出(在转到操场上尝试):

[0 1 2 3 4 5 6 7 8 9]

请注意,我使用了 runtime.KeepAlive() .这是因为在获取了arr的地址并获得其长度之后,我们不再引用arr(puintptr不再作为引用),并且积极的GC可能-理应地-在到达要打印data的点(指向arr)之前,请擦除arr.将runtime.KeepAlive()放在main()的末尾将确保不会收集arr 在这个电话之前.有关详细信息,请参见在Go中,变量何时会变为如果指针的提供者确保不会被垃圾回收,则无需在代码中调用runtime.KeepAlive().

Note that I used runtime.KeepAlive(). This is because after taking the address of arr and getting its length, we don't refer to arr anymore (p being uintptr does not count as a reference), and an aggressive GC might–rightfully–erase arr before we get to the point to print data (pointing to arr). Placing a runtime.KeepAlive() to the end of main() will ensure that arr will not be garbage collected before this call. For details, see In Go, when will a variable become unreachable? You do not need to call runtime.KeepAlive() in your code if the supplier of the pointer ensures it will not be garbage collected.

或者,您可以使用复合文字创建reflect.SliceHeader,并使用不安全的转换来从中获取切片,像这样:

Alternatively you can create a reflect.SliceHeader with a composite literal, and use unsafe conversions to obtain a slice from it, like this:

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

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

fmt.Println(data)

runtime.KeepAlive(arr)

输出将相同.在去游乐场上尝试一下.

Output will be the same. Try this one on the Go Playground.

这种可能性/用例记录在 unsafe.Pointer 中,注意事项和警告:

(6)将reflect.SliceHeader或reflect.StringHeader数据字段与指针进行转换.

(6) Conversion of a reflect.SliceHeader or reflect.StringHeader Data field to or from Pointer.

与前面的情况一样,反射数据结构SliceHeader和StringHeader将字段Data声明为uintptr,以防止调用者将结果更改为任意类型,而无需首先导入"unsafe".但是,这意味着SliceHeader和StringHeader仅在解释实际切片或字符串值的内容时才有效.

As in the previous case, the reflect data structures SliceHeader and StringHeader declare the field Data as a uintptr to keep callers from changing the result to an arbitrary type without first importing "unsafe". However, this means that SliceHeader and StringHeader are only valid when interpreting the content of an actual slice or string value.

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变量本身.

In this usage hdr.Data is really an alternate way to refer to the underlying pointer in the slice header, not a uintptr variable itself.

通常,reflect.SliceHeader和reflect.StringHeader只能用作指向实际切片或字符串的* reflect.SliceHeader和* reflect.StringHeader,而不能用作纯结构.程序不应声明或分配这些结构类型的变量.

In general, reflect.SliceHeader and reflect.StringHeader should be used only as *reflect.SliceHeader and *reflect.StringHeader pointing at actual slices or strings, never as plain structs. A program should not declare or allocate variables of these struct types.

// 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

这篇关于如何使用golang中的unsafe.Pointer指针从数组创建数组或切片?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆