57

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

4

9 回答 9

56

正如其他人所说,转换指针在 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))
于 2012-08-13T01:55:34.130 回答
9

你做你在 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 的字节在不同的计算机上的布局可能不同,所以你不应该假设这些是你需要的布局。

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

亚历克斯

于 2012-08-13T00:10:17.000 回答
8

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

于 2012-08-12T19:33:35.110 回答
2

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

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

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

于 2013-07-29T18:32:07.257 回答
1

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

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

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

于 2019-03-06T16:50:50.940 回答
1

您可以使用“不安全”包来做到这一点

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
 */
于 2016-10-11T17:57:51.293 回答
1

从 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)
}
于 2021-08-25T21:17:40.143 回答
1

转到 1.17 及更高版本

Go 1.17引入了这个unsafe.Slice函数,它正是这样做的。

将 a 转换[]byte为 a []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)
    }
}

游乐场

于 2021-10-22T11:29:35.203 回答
0

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, ", "))
}
于 2014-05-19T10:30:15.427 回答