如何在 golang 中从数组 unsafe.Pointer 创建数组或切片?
How to create an array or a slice from an array unsafe.Pointer in golang?
指向数组的指针,比方说:
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)
但是这种方法做的是内存copy,效率不高,有没有不做copy的?
P.S。我也尝试了以下两种方法,
// method 1
data := *(*[]byte)(unsafe.Pointer(p))
// method 2
data := *(*[size]byte)(unsafe.Pointer(p))
但它会在运行时失败,我现在知道它的原因了。
前言:
您应该知道:如果您将指针作为 uintptr
类型的值,这不会阻止原始数组被垃圾收集(uintptr
值不算作引用).所以在使用这样的值时要小心,不能保证它会指向一个有效的值/内存区域。
引用包 unsafe.Pointer
:
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 的类型安全中。
声明一个切片类型的变量,并使用不安全的转换来获取它的reflect.SliceHeader
描述符。
然后可以修改它的字段,使用指针作为SliceHeader.Data
值,大小为SliceHeader.Len
和SliceHeader.Cap
。
完成此操作后,切片变量将指向与初始指针相同的数组。
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
的地址并获得其长度后,我们不再引用 arr
(p
为 uintptr
不算作引用),在我们到达打印 data
(指向 arr
)之前,激进的 GC 可能会正确地擦除 arr
。将 runtime.KeepAlive()
放在 main()
的末尾将确保 arr
不会被垃圾收集
在这个电话之前。有关详细信息,请参阅 如果指针的提供者确保它不会被垃圾回收,则无需在代码中调用 runtime.KeepAlive()
。
或者您可以创建一个 reflect.SliceHeader
和 composite literal,然后使用不安全的转换从中获取切片,如下所示:
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) Conversion of a reflect.SliceHeader or reflect.StringHeader Data field to or from 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". 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
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.
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
使用 Go 1.17,我们现在有 unsafe.Slice(ptr *ArbitraryType, len IntegerType)
。
您可以使用此函数创建一个切片,其底层数组从 ptr
开始,长度和容量为 len
.
假设你有一个数组:
arr := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
如果您有一个名为 p
的 unsafe.Pointer
:
p := unsafe.Pointer(&arr); // Or unsafe.Pointer(&arr[0])
data := unsafe.Slice((*byte)(p), len(arr));
但如果您可以直接访问 arr
,则可以将其简化为:
data := unsafe.Slice(&arr[0], len(arr));
提醒一句:如果直接传递指针,请务必使用 &arr[0]
而 而不是 &arr
。 &arr
是 *[10]byte
,而不是 *byte
。使用 &arr
将创建长度和容量为 10 的 [][10]byte
,而不是长度和容量为 10 的 []byte
。
使用 Go 1.17,我们也可以 conveniently cast from a slice to an array pointer。简而言之,如果您使用切片而不是 unsafe.Pointer(p)
:
,您的 method 2
会起作用
array := *(*[len(arr)]byte)(data)
指向数组的指针,比方说:
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)
但是这种方法做的是内存copy,效率不高,有没有不做copy的?
P.S。我也尝试了以下两种方法,
// method 1
data := *(*[]byte)(unsafe.Pointer(p))
// method 2
data := *(*[size]byte)(unsafe.Pointer(p))
但它会在运行时失败,我现在知道它的原因了。
前言:
您应该知道:如果您将指针作为 uintptr
类型的值,这不会阻止原始数组被垃圾收集(uintptr
值不算作引用).所以在使用这样的值时要小心,不能保证它会指向一个有效的值/内存区域。
引用包 unsafe.Pointer
:
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 的类型安全中。
声明一个切片类型的变量,并使用不安全的转换来获取它的reflect.SliceHeader
描述符。
然后可以修改它的字段,使用指针作为SliceHeader.Data
值,大小为SliceHeader.Len
和SliceHeader.Cap
。
完成此操作后,切片变量将指向与初始指针相同的数组。
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
的地址并获得其长度后,我们不再引用 arr
(p
为 uintptr
不算作引用),在我们到达打印 data
(指向 arr
)之前,激进的 GC 可能会正确地擦除 arr
。将 runtime.KeepAlive()
放在 main()
的末尾将确保 arr
不会被垃圾收集
在这个电话之前。有关详细信息,请参阅 runtime.KeepAlive()
。
或者您可以创建一个 reflect.SliceHeader
和 composite literal,然后使用不安全的转换从中获取切片,如下所示:
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) Conversion of a reflect.SliceHeader or reflect.StringHeader Data field to or from 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". 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
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.
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
使用 Go 1.17,我们现在有 unsafe.Slice(ptr *ArbitraryType, len IntegerType)
。
您可以使用此函数创建一个切片,其底层数组从 ptr
开始,长度和容量为 len
.
假设你有一个数组:
arr := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
如果您有一个名为 p
的 unsafe.Pointer
:
p := unsafe.Pointer(&arr); // Or unsafe.Pointer(&arr[0])
data := unsafe.Slice((*byte)(p), len(arr));
但如果您可以直接访问 arr
,则可以将其简化为:
data := unsafe.Slice(&arr[0], len(arr));
提醒一句:如果直接传递指针,请务必使用 &arr[0]
而 而不是 &arr
。 &arr
是 *[10]byte
,而不是 *byte
。使用 &arr
将创建长度和容量为 10 的 [][10]byte
,而不是长度和容量为 10 的 []byte
。
使用 Go 1.17,我们也可以 conveniently cast from a slice to an array pointer。简而言之,如果您使用切片而不是 unsafe.Pointer(p)
:
method 2
会起作用
array := *(*[len(arr)]byte)(data)