登录
首页 >  Golang >  Go问答

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

来源:Golang技术栈

时间:2023-04-12 17:19:18 196浏览 收藏

对于一个Golang开发者来说,牢固扎实的基础是十分重要的,golang学习网就来带大家一点点的掌握基础知识点。今天本篇文章带大家了解《如何从 golang 中的数组 unsafe.Pointer 创建数组或切片?》,主要介绍了golang,希望对大家的知识积累有所帮助,快点收藏起来吧,否则需要时就找不到了!

问题内容

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

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

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

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

现在,我想用已知的指针、大小,当然还有数据类型来初始化切片或数组。

我想出了以下代码:

data := make([]byte, size)
stepSize := unsafe.Sizeof(data[0])
for i := 0; i 

但是这种方法会进行内存复制,这可能效率低下,无论如何不进行复制吗?

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

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

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

正确答案

前言:

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

从包中引用unsafe.Pointer

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

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


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

然后你可以修改它的字段,使用指针作为SliceHeader.Data值,大小作为SliceHeader.Lenand 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并获取其长度之后,我们不再引用arrpbeinguintptr不算作引用),并且arr在我们到达打印点之前,一个激进的 GC 可能会“轻而易举”地删除data(指向arr)。将 aruntime.KeepAlive()放在末尾main()将确保arr在此调用之前不会被垃圾收集。有关详细信息,请参阅[在 Go 中,变量何时会变得不可访问?](https://stackoverflow.com/questions/37588639/in-go-when-will- a-variable-become- unreachable/37591282#37591282)runtime.KeepAlive()如果指针的提供者确保它不会被垃圾收集,则不需要调用代码。

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

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 数据字段与 Pointer 的转换。

与前一种情况一样,反射数据结构 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

到这里,我们也就讲完了《如何从 golang 中的数组 unsafe.Pointer 创建数组或切片?》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于golang的知识点!

声明:本文转载于:Golang技术栈 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>
评论列表