【问题标题】:How can I cast []byte to [8]uint8如何将 []byte 转换为 [8]uint8
【发布时间】:2019-03-07 15:39:54
【问题描述】:

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

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

给予

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

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

【问题讨论】:

    标签: go


    【解决方案1】:

    背景

    您的“简单方法”的问题在于,切片 (任何类型)是一个由指针组成的struct-typed 和两个整数;指针包含的地址 底层(支持)数据数组,整数包含 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 个 uint8s" 的数组。

    注意两个警告:

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

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

      我特别提到这一点,因为人们经常使用 像这样的黑客来拉出支持阵列 精确切片以尝试不复制内存。

    TL;DR

    复制数据(假设验证了 len(src) == len(dst) 保持不变)。

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

    只有当你真的 需要在一些关键的热路径上进行优化。 在这种情况下,请广泛评论解决方案并注意 不会意外取消引用您的指针。


    ¹这条规则有一些明显的例外:

    • []byte 可以类型转换为 string,反之亦然。
    • string 可以类型转换为 []rune,反之亦然。
    • int 可以类型转换为 string(但从 Go 1.15 开始,go vetgives a warning about it,将来可能会禁止此功能)。

    【讨论】:

      【解决方案2】:

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

      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.

      但是我可以问你为什么使用数组吗?通常最好只使用切片,除非你有很好的理由。

      【讨论】:

        【解决方案3】:

        从 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 的别名(等同于)。

        相关:How do you convert a slice into an array?

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-10-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-08
          相关资源
          最近更新 更多