【问题标题】:Convert between slices of different types在不同类型的切片之间转换
【发布时间】:2012-08-09 02:03:27
【问题描述】:

我从 UDP 套接字获得一个字节切片 ([]byte),并希望在不更改底层数组的情况下将其视为整数切片 ([]int32),反之亦然。在 C(++) 中,我只会在指针类型之间进行转换;我将如何在 Go 中执行此操作?

【问题讨论】:

    标签: go go-reflect


    【解决方案1】:

    正如其他人所说,转换指针在 Go 中被认为是错误的形式。以下是正确的 Go 方式和 C 数组强制转换等价物的示例。

    警告:所有代码未经测试。

    正确的方法

    在本例中,我们使用 encoding/binary 包将每组 4 个字节转换为 int32。这更好,因为我们指定了字节序。我们也没有使用unsafe 包来破坏类型系统。

    import "encoding/binary"
    
    const SIZEOF_INT32 = 4 // bytes
    
    data := make([]int32, len(raw)/SIZEOF_INT32)
    for i := range data {
        // assuming little endian
        data[i] = int32(binary.LittleEndian.Uint32(raw[i*SIZEOF_INT32:(i+1)*SIZEOF_INT32]))
    }
    

    错误的方式(C 数组转换)

    在这个例子中,我们告诉 Go 忽略类型系统。这不是一个好主意,因为它可能在 Go 的另一个实现中失败。它假设语言规范中没有的东西。但是,这个并没有做一个完整的副本。此代码使用 unsafe 访问所有切片中通用的“SliceHeader”。标头包含指向数据(C 数组)、长度和容量的指针。我们首先需要更改长度和容量,而不是将标头转换为新的切片类型,因为如果我们将字节视为新类型,则元素会更少。

    import (
        "reflect"
        "unsafe"
    )
    
    const SIZEOF_INT32 = 4 // bytes
    
    // Get the slice header
    header := *(*reflect.SliceHeader)(unsafe.Pointer(&raw))
    
    // The length and capacity of the slice are different.
    header.Len /= SIZEOF_INT32
    header.Cap /= SIZEOF_INT32
    
    // Convert slice header to an []int32
    data := *(*[]int32)(unsafe.Pointer(&header))
    

    【讨论】:

    • 当然“正确”的方式复制数据,“错误”的方式使用原地的数据。
    • Go 的内存安全性及其内存实现的一部分,它不是语言的保证。虽然另一方面,我无法想象切片将是不连续的......
    【解决方案2】:

    简短的回答是你不能。 Go 不会让你将一种类型的切片转换为另一种类型的切片。在转换数组中的每个项目时,您将遍历数组并创建另一个所需类型的数组。这通常被认为是一件好事,因为类型安全是 go 的一个重要特性。

    【讨论】:

    • 允许int8(int16(a) 似乎不一致(如果a > 255 在运行时崩溃,但禁止[]int8([]int16(a),这同样(不)安全(并且在像@ 这样的情况下是完全安全的) 987654324@)。哦,好吧。
    • 你可能会这么认为,但这是 Go 类型系统中的两种不同情况,不能真正混淆。 Go 的类型系统在理论上并不完美,而是旨在提高开发人员的生产力。
    • 好吧,在这种情况下,类型系统不会提高开发人员的工作效率。我并不是建议 Go 应该以牺牲其他任何东西为代价来支持这种情况,只是指出这是一个设计缺陷。我很确定这是不一致的。
    【解决方案3】:

    你做你在 C 中所做的事情,除了一个例外 - Go 不允许从一种指针类型转换为另一种。好吧,确实如此,但是您必须使用 unsafe.Pointer 告诉编译器您知道所有规则都被破坏并且您知道自己在做什么。这是一个例子:

    package main
    
    import (
        "fmt"
        "unsafe"
    )
    
    func main() {
        b := []byte{1, 0, 0, 0, 2, 0, 0, 0}
    
        // step by step
        pb := &b[0]         // to pointer to the first byte of b
        up := unsafe.Pointer(pb)    // to *special* unsafe.Pointer, it can be converted to any pointer
        pi := (*[2]uint32)(up)      // to pointer to the first uint32 of array of 2 uint32s
        i := (*pi)[:]           // creates slice to our array of 2 uint32s (optional step)
        fmt.Printf("b=%v i=%v\n", b, i)
    
        // all in one go
        p := (*[2]uint32)(unsafe.Pointer(&b[0]))
        fmt.Printf("b=%v p=%v\n", b, p)
    }
    

    显然,你应该小心使用“不安全”包,因为 Go 编译器不再牵着你的手 - 例如,你可以在这里写 pi := (*[3]uint32)(up) 编译器不会抱怨,但你会遇到麻烦。

    另外,正如其他人已经指出的那样,uint32 的字节在不同的计算机上可能会有不同的布局,所以你不应该假设这些是你需要的布局。

    所以最安全的方法是一个接一个地读取字节数组,然后从中取出你需要的任何东西。

    亚历克斯

    【讨论】:

    • 这会将其转换为固定大小的数组。如果在编译时不知道 int32 的数量,这是行不通的。
    • 为什么不呢?我可以说这个数组和我想要的一样大。
    • 是的,你可以。但是你只能在编译时这样做。您不能将其转换为 [n]uint32 ,其中 n 是可变的。
    • 你说的都是对的。在您的示例中,您将[]byte 转换为[2]uint32。那是一个数组。我试图指出,如果您不知道在编译时转换了多少个 int32,那么您使用数组的方法将不起作用。
    • 当然你可能不知道你的数据有多大,但你总是可以说它小于某个 const 值。例如,您可以将其转换为 [1
    【解决方案4】:

    我遇到了大小未知的问题,并使用以下代码调整了以前的不安全方法。 给定一个字节切片 b ...

    int32 slice is (*(*[]int)(Pointer(&b)))[:len(b)/4]
    

    数组到切片的例子可能会被赋予一个虚构的大常数,并且切片边界以相同的方式使用,因为没有分配数组。

    【讨论】:

      【解决方案5】:

      你可以用“不安全”的包来做到这一点

      package main
      
      import (
          "fmt"
          "unsafe"
      )
      
      func main() {
          var b [8]byte = [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
          var s *[4]uint16 = (*[4]uint16)(unsafe.Pointer(&b))
          var i *[2]uint32 = (*[2]uint32)(unsafe.Pointer(&b))
          var l *uint64 = (*uint64)(unsafe.Pointer(&b))
      
          fmt.Println(b)
          fmt.Printf("%04x, %04x, %04x, %04x\n", s[0], s[1], s[2], s[3])
          fmt.Printf("%08x, %08x\n", i[0], i[1])
          fmt.Printf("%016x\n", *l)
      }
      
      /*
       * example run:
       * $ go run /tmp/test.go
       * [1 2 3 4 5 6 7 8]
       * 0201, 0403, 0605, 0807
       * 04030201, 08070605
       * 0807060504030201
       */
      

      【讨论】:

        【解决方案6】:

        可能在给出较早的答案时它不可用,但似乎binary.Read 方法会比上面给出的“正确方法”更好的答案。

        此方法允许您将二进制数据从读取器直接读取到所需类型的值或缓冲区中。您可以通过在字节数组缓冲区上创建读取器来做到这一点。或者,如果您可以控制为您提供字节数组的代码,则可以将其替换为直接读入缓冲区,而无需临时字节数组。

        请参阅 https://golang.org/pkg/encoding/binary/#Read 获取文档和一个不错的小示例。

        【讨论】:

          【解决方案7】:

          从 Go 1.17 开始,使用 unsafe 包有一种更简单的方法。

          import (
              "unsafe"
          )
          
          const SIZEOF_INT32 = unsafe.Sizeof(int32(0)) // 4 bytes
          
          func main() {
              var bs []byte
              
              // Do stuff with `bs`. Maybe do some checks ensuring that len(bs) % SIZEOF_INT32 == 0
              
              data := unsafe.Slice((*int32)(unsafe.Pointer(&bs[0])), len(bs)/SIZEOF_INT32)
          
              // A more verbose alternative requiring `import "reflect"`
              // data := unsafe.Slice((*int32)(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&bs)).Data)), len(bs)/SIZEOF_INT32)
          }
          

          【讨论】:

            【解决方案8】:

            转到 1.17 及更高版本

            Go 1.17 introduced unsafe.Slice 函数,正是这样做的。

            []byte 转换为[]int32

            package main
            
            import (
                "fmt"
                "unsafe"
            )
            
            func main() {
                theBytes := []byte{
                    0x33, 0x44, 0x55, 0x66,
                    0x11, 0x22, 0x33, 0x44,
                    0x77, 0x66, 0x55, 0x44,
                }
            
                numInts := uintptr(len(theBytes)) * unsafe.Sizeof(theBytes[0]) / unsafe.Sizeof(int32(0))
                theInts := unsafe.Slice((*int32)(unsafe.Pointer(&theBytes[0])), numInts)
            
                for _, n := range theInts {
                    fmt.Printf("%04x\n", n)
                }
            }
            

            Playground.

            【讨论】:

              【解决方案9】:

              http://play.golang.org/p/w1m5Cs-ecz

              package main
              
              import (
                  "fmt"
                  "strings"
              )
              
              func main() {
                  s := []interface{}{"foo", "bar", "baz"}
                  b := make([]string, len(s))
                  for i, v := range s {
                      b[i] = v.(string)
                  }
                  fmt.Println(strings.Join(b, ", "))
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-09-27
                • 2021-08-12
                相关资源
                最近更新 更多