如何将[] byte转换为[8] uint8

时间:2018-10-02 13:38:48

标签: go

我需要填充一个成员类型为[8]uint8的结构。需要使用初始化为长度8的[]byte类型的字节数组来填充。简单的方法不起作用:

Data:   [8]uint8(RequestFrame(0x180, r)),

给予

cannot convert .. (type []byte) to type [8]uint8

由于两个数组在结构上都是相同的,如果可以通过强制转换/赋值而不是复制来完成,那会很好吗?

3 个答案:

答案 0 :(得分:2)

您可以使用byte非常简单地将uint8切片的内容复制到copy数组中,如下所示:

package main

import (
    "fmt"
)

func main() {
    slice := []byte{1, 2, 3, 4, 5, 6, 7, 8}

    array := [8]uint8{}

    copy(array[:], slice)

    fmt.Println(array)
}

输出

[1 2 3 4 5 6 7 8]

Try it out on the playground

但是请问您为什么要使用数组?除非您有充分的理由,否则通常最好只使用切片。

答案 1 :(得分:2)

背景

您的“简单化方法”存在的问题是 (任何类型)是一个struct类型的指针 和两个整数;指针包含 基础(支持)数据数组,并且整数包含 该切片将返回什么len()cap()内建函数。

换句话说,切片是对数组的视图。

然后,在Go语言中,没有类型转换的概念。只有 类型转换,而这些转换可能只发生在 具有相同基本表示¹的类型。

由于切片和数组可能没有相同的基础 表示(数组实际上是一个连续的内存块 大小足以容纳数组的所有元素), 您指控的类型转换可能不合法。

可能的解决方案

有两种可能的解决方案。

最简单的方法是仅复制切片的数据 将数组备份到新分配的数组中:

var (
    src = []byte{1, 2, 3, 4, 5, 6, 7, 8}
    dst [8]uint8
)
copy(dst[:], src[:8])

请注意,切片和 数组类型:数组类型同时编码其元素的类型 及其长度(即长度是类型的一部分), 切片类型仅编码其元素的类型 (并且在运行时可以是任意长度)。

这意味着您可能需要先检查一下 复制以确保源切片精确地有8个 元素,即len(src) == len(dst)

此不变式可能由其他一些代码强制执行,但我认为 我会事先警告您:如果src少于8 元素,src[:8]表达式将在运行时出现恐慌, 如果它包含更多,那么就有一个问题是 只需复制其中的前8个即可。

第二种方法(公认更混乱)是重复使用 切片的基础数组:

import "unsafe"

var (
    src    = []byte{1, 2, 3, 4, 5, 6, 7, 8}
    dstPtr *[8]uint8
)

if len(src) != len(*dstPtr) {
    panic("boom")
}
dstPtr = (*[8]uint8)(unsafe.Pointer(&src[0]))

在这里,我们已经获取了第一个元素的地址 包含在切片的基础数组中并执行 一个“肮脏的”两阶段类型转换,使获得 类型为*[8]uint8的指针,即“ 8 uint8 s的数组”。

请注意两个注意事项:

  • 现在得到的指针指向 原始切片执行相同的内存块。 这意味着现在可以通过 切片和我们获得的指针。

  • 一旦您决定分配数组的数据 到类型[8]uint8的变量中(并将其作为参数传递 到该类型的函数的参数),您将取消引用 该指针(例如*dstPtr),并在那时 阵列的数据将被复制。

    我特别提到这是人们经常诉诸的地方 像这样的黑客将支持阵列拉出 精确地尝试切片,以免复制内存。

TL; DR

复制数据(假设已验证 len(src) == len(dst)不变成立。

快速复制8个字节(在典型的64位CPU上,这将是 一条MOV指令,或最多两个),代码将 简单明了。

只有当您真正使用第二种解决方案时,才可以使用黑客手段 需要在一些关键的热路径上进行优化。 在这种情况下,请广泛评论解决方案并注意 不会意外取消引用指针。


¹此规则有一个但值得注意的例外: []byte可以类型转换为string,反之亦然。

答案 2 :(得分:1)

从 Go 1.17 开始,您可以直接使用类型转换,从 slice to an array pointer:

    a := make([]byte, 8)
    b := (*[8]uint8)(a) // b is pointer to [8]uint8

您可以直接取消引用以获得非指针 [8]uint8 类型。

    a := make([]byte, 8)
    b := *(*[8]uint8)(a) // b is [8]uint8

注意事项:

  • copy 不同,转换方法不会产生额外的分配(不是你的,也不是 copy 可能完成的),因为它只是产生一个指向现有的支持数组。虽然取消引用数组指针 will make a copy
  • 如果数组的长度大于切片的长度,则转换会出错
a := make([]byte, 5)
b := (*[10]byte)(a) // panics
  • 指针指向切片的底层数组,因此通过索引可以看到相同的值:
    a := []byte{0xa1, 0xa2}
    b := (*[2]uint8)(a)

    fmt.Printf("%x\n", a[0]) // a1
    b[0] = 0xff
    fmt.Printf("%x\n", a[0]) // ff
  • 您可以将 byte 转换为 uint8,包括从它们派生的类型文字,因为 byte 是 uint8 的别名(等同于)。