在不同类型的切片之间转换

sla*_*ast 40 go go-reflect

[]byte从UDP套接字获得一个字节slice()并希望将其视为整数slice([]int32)而不更改底层数组,反之亦然.在C(++)中,我只是在指针类型之间转换; 我怎么在Go中这样做?

Ste*_*erg 40

正如其他人所说,在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]))
}
Run Code Online (Sandbox Code Playgroud)

错误的方式(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))
Run Code Online (Sandbox Code Playgroud)

  • 当然,"正确"的方式复制数据,"错误"的方式使用数据到位. (21认同)
  • 当切片中有数百万个项目(即图像中的像素)时,“正确的方法”性能不是很好 (3认同)

Jer*_*all 8

简短的回答是你不能.不要让你把一种类型的切片投射到另一种类型的切片上.您将循环遍历数组并在投射数组中的每个项目时创建所需类型的另一个数组.这通常被认为是一件好事,因为类型安全是一个重要的特征.

  • 那么,在这种情况下,类型系统不会提高开发人员的工作效率.我并不是说Go应该改变以支持这种情况而牺牲其他任何东西,只是指出它是一个设计疣.我非常有信心这是一个不一致的地方. (5认同)
  • 允许`int8(int16(a)`(如果`a> 255`则在运行时崩溃,但禁止`[] int8([] int16(a)`,这同样(不)安全(并且会在类似`type myint int; [] myint([] int {1})`)的情况下是完全安全的.哦,好吧. (3认同)
  • @misterbee在运行时不崩溃:https://play.golang.org/p/R_3WmTEXOs (2认同)

Yen*_*ang 6

从 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)
}
Run Code Online (Sandbox Code Playgroud)


ale*_*lex 5

你做你在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)
}
Run Code Online (Sandbox Code Playgroud)

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

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

所以最安全的方法是逐个读取你的字节数组,并从中获取你需要的任何东西.

亚历克斯