简体   繁体   English

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

[英]How to create an array or a slice from an array unsafe.Pointer in golang?

A pointer to an array, let's say: 一个指向数组的指针,让我们说:

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

I can't access to the variable array , the code above is used to make it more clear. 我无法访问变量array ,上面的代码用于使其更清晰。

Also, I know the size of the array, but the size is not constant, it changes according to the runtime. 另外,我知道数组的size ,但是size不是常量,它会根据运行时间而变化。

Now, I want to initialize a slice or an array with the known pointer, size and of course the data type. 现在,我想用已知的指针,大小以及数据类型初始化切片或数组。

I come up with the following code: 我想出了以下代码:

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? 但是这个方法会进行内存复制,这可能是效率低下的,无论如何都没有复制?

PS I also tried the following two methods, PS我也试过以下两种方法,

// 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. 但它会在运行时失败,我现在知道它的原因。

Foreword: 前言:

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). 您应该知道:如果您将指针作为uintptr类型的值,则不会阻止原始数组进行垃圾回收( uintptr值不计入引用)。 So be careful when using such value, there is no guarantee it will point to a valid value / memory area. 所以在使用这样的值时要小心,不能保证它会指向有效的值/内存区域。

Quoting from package unsafe.Pointer : 从包unsafe.Pointer引用:

A uintptr is an integer, not a reference. uintptr是整数,而不是引用。 Converting a Pointer to a uintptr creates an integer value with no pointer semantics. 将指针转换为uintptr会创建一个没有指针语义的整数值。 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. 即使uintptr保存某个对象的地址,如果对象移动,垃圾收集器也不会更新该uintptr的值,uintptr也不会保留该对象的回收。

General advice: stay away from package unsafe as much as possible. 一般建议:尽可能远离包装unsafe Stay inside Go's type safety. 留在Go的类型安全。


Declare a variable of slice type, and use unsafe conversions to obtain its reflect.SliceHeader descriptor. 声明切片类型的变量,并使用不安全的转换来获取其reflect.SliceHeader描述符。

Then you can modify its fields, using the pointer as the SliceHeader.Data value, and the size as SliceHeader.Len and SliceHeader.Cap . 然后,您可以使用指针作为SliceHeader.Data值修改其字段,并将大小修改为SliceHeader.LenSliceHeader.Cap

Once you're done with this, the slice variable will point to the same array as your initial pointer. 完成此操作后,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)

Output (try it on the Go Playground ): 输出(在Go Playground上试试):

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

Note that I used runtime.KeepAlive() . 请注意,我使用了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 ). 这是因为在获取arr的地址并获取其长度之后,我们不再引用arrpuintptr不算作参考),并且积极的GC可能在我们到达点之前正确地擦除arr打印data (指向arr )。 Placing a runtime.KeepAlive() to the end of main() will ensure that arr will not be garbage collected before this call. runtime.KeepAlive()放在main()的末尾将确保在此调用之前不会对arr进行垃圾回收。 For details, see In Go, when will a variable become unreachable? 有关详细信息,请参阅In Go,变量何时无法访问? You do not need to call runtime.KeepAlive() in your code if the supplier of the pointer ensures it will not be garbage collected. 如果指针的供应商确保它不会被垃圾收集,则不需要在代码中调用runtime.KeepAlive()

Alternatively you can create a reflect.SliceHeader with a composite literal , and use unsafe conversions to obtain a slice from it, like this: 或者,您可以使用复合文字创建一个reflect.SliceHeader ,并使用不安全的转换从中获取切片,如下所示:

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 . Go Playground尝试这个。

This possibility / use case is documented at unsafe.Pointer , with the caveats and warnings: 这个可能性/用例记录在unsafe.Pointer ,包含警告和警告:

(6) Conversion of a reflect.SliceHeader or reflect.StringHeader Data field to or from Pointer. (6)将一个reflect.SliceHeader或reflect.StringHeader数据字段转换为Pointer或从指针转换。

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". 与前一种情况一样,反射数据结构SliceHeader和StringHeader将字段Data声明为uintptr,以防止调用者将结果更改为任意类型,而无需先导入“unsafe”。 However, this means that SliceHeader and StringHeader are only valid when interpreting the content of an actual slice or string value. 但是,这意味着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 

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. 在这种用法中,hdr.Data实际上是一种引用切片头中的底层指针的替代方法,而不是uintptr变量本身。

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. 通常,reflect.SliceHeader和reflect.StringHeader只能用作* reflect.SliceHeader和* reflect.StringHeader指向实际的切片或字符串,而不是普通的结构。 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 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM