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

huangapple go评论166阅读模式
英文:

Convert between slices of different types

问题

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

英文:

I get a byte slice ([]byte) from a UDP socket and want to treat it as an integer slice ([]int32) without changing the underlying array, and vice versa. In C(++) I would just cast between pointer types; how would I do this in Go?

答案1

得分: 60

正如其他人所说,Go语言中的指针转换被认为是不好的形式。以下是正确的Go语言方式和等效的C数组转换的示例。

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

正确的方式

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

import "encoding/binary"

const SIZEOF_INT32 = 4 // 字节

data := make([]int32, len(raw)/SIZEOF_INT32)
for i := range data {
    // 假设是小端字节序
    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 // 字节

// 获取切片头部
header := *(*reflect.SliceHeader)(unsafe.Pointer(&raw))

// 切片的长度和容量不同。
header.Len /= SIZEOF_INT32
header.Cap /= SIZEOF_INT32

// 将切片头部转换为[]int32
data := *(*[]int32)(unsafe.Pointer(&header))
英文:

As others have said, casting the pointer is considered bad form in Go. Here are examples of the proper Go way and the equivalent of the C array casting.

WARNING: all code untested.

The Right Way

In this example, we are using the encoding/binary package to convert each set of 4 bytes into an int32. This is better because we are specifying the endianness. We are also not using the unsafe package to break the type system.

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]))
}

The Wrong Way (C array casting)

In this example, we are telling Go to ignore the type system. This is not a good idea because it may fail in another implementation of Go. It is assuming things not in the language specification. However, this one does not do a full copy. This code uses unsafe to access the "SliceHeader" which is common in all slices. The header contains a pointer to the data (C array), the length, and the capacity. Instead of just converting the header to the new slice type, we first need to change the length and capacity since there are less elements if we treat the bytes as a new type.

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))

答案2

得分: 10

你在C中做的事情在Go中也可以做,只有一个例外 - Go不允许将一个指针类型转换为另一个指针类型。嗯,它是可以的,但是你必须使用unsafe.Pointer来告诉编译器你知道所有规则都被打破了,你知道自己在做什么。这里有一个例子:

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    b := []byte{1, 0, 0, 0, 2, 0, 0, 0}

    // 逐步进行
    pb := &b[0]            // 指向b的第一个字节的指针
    up := unsafe.Pointer(pb)    // 转换为*特殊*的unsafe.Pointer,它可以转换为任何指针
    pi := (*[2]uint32)(up)        // 指向2个uint32数组的第一个uint32的指针
    i := (*pi)[:]            // 创建对我们的2个uint32数组的切片(可选步骤)
    fmt.Printf("b=%v i=%v\n", b, i)

    // 一次性完成
    p := (*[2]uint32)(unsafe.Pointer(&b[0]))
    fmt.Printf("b=%v p=%v\n", b, p)
}

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

另外,正如其他人已经指出的,uint32的字节在不同的计算机上可能有不同的布局,所以你不应该假设它们的布局符合你的需求。

因此,最安全的方法是逐个读取字节数组,并根据需要进行处理。

Alex

英文:

You do what you do in C, with one exception - Go does not allow to convert from one pointer type to another. Well, it does, but you must use unsafe.Pointer to tell compiler that you are aware that all rules are broken and you know what you are doing. Here is an example:

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)
}

Obviously, you should be careful about using "unsafe" package, because Go compiler is not holding your hand anymore - for example, you could write pi := (*[3]uint32)(up) here and compiler wouldn't complain, but you would be in trouble.

Also, as other people pointed already, bytes of uint32 might be layout differently on different computers, so you should not assume these are layout as you need them to be.

So safest approach would be to read your array of bytes one by one and make whatever you need out of them.

Alex

答案3

得分: 7

短答案是你不能。Go不允许你将一个类型的切片转换为另一个类型的切片。你需要遍历数组,并在转换数组中的每个项时创建另一个你想要的类型的数组。这通常被认为是一件好事,因为类型安全是Go的一个重要特性。

英文:

The short answer is you can't. Go wont let you cast a slice of one type to a slice of another type. You will have loop through the array and create another array of the type you want while casting each item in the array. This is generally regarded as a good thing since typesafety is an important feature of go.

答案4

得分: 6

自Go 1.17版本以来,使用unsafe包有一种更简单的方法来实现这一点。

import (
    "unsafe"
)

const SIZEOF_INT32 = unsafe.Sizeof(int32(0)) // 4 bytes

func main() {
    var bs []byte
    
    // 使用`bs`进行一些操作。可能需要进行一些检查,确保len(bs) % SIZEOF_INT32 == 0
    
    data := unsafe.Slice((*int32)(unsafe.Pointer(&bs[0])), len(bs)/SIZEOF_INT32)

    // 一种更冗长的替代方法,需要导入`reflect`包
    // data := unsafe.Slice((*int32)(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&bs)).Data)), len(bs)/SIZEOF_INT32)
}
英文:

Since Go 1.17, there is a simpler way to do this using the unsafe package.

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)
}

答案5

得分: 4

Go 1.17及更高版本

Go 1.17 引入了 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.

英文:

Go 1.17 and beyond

Go 1.17 introduced the unsafe.Slice function, which does exactly this.

Converting a []byte to 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)
    }
}

Playground.

答案6

得分: 2

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

int32切片是(*(*[]int)(Pointer(&b)))[:len(b)/4]

数组切片示例可以给定一个虚构的大常数,并以相同的方式使用切片边界,因为没有分配数组。

英文:

I had the size unknown problem and tweaked the previous unsafe method with the following code.
given a byte slice b ...

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

The array to slice example may be given a fictional large constant and the slice bounds used in the same way since no array is allocated.

答案7

得分: 1

你可以使用"unsafe"包来完成这个任务。

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
 */
英文:

You can do it with the "unsafe" package

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
 */

答案8

得分: 1

也许在之前的回答中没有提到,但是binary.Read方法似乎是比上面给出的“正确方法”更好的答案。

这个方法允许你直接从读取器中将二进制数据读取到你想要的类型的值或缓冲区中。你可以通过在字节数组缓冲区上创建一个读取器来实现这一点。或者,如果你可以控制给你字节数组的代码,你可以将其替换为直接读取到你的缓冲区中,而不需要中间的字节数组。

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

英文:

Perhaps it was not available when the earlier answers were given, but it would seem that the binary.Read method would be a better answer than "the right way" given above.

This method allows you to read binary data from a reader directly into the value or buffer of your desired type. You can do this by creating a reader over your byte array buffer. Or, if you have control of the code that is giving you the byte array, you can replace it to read directly into your buffer without the need for the interim byte array.

See https://golang.org/pkg/encoding/binary/#Read for the documentation and a nice little example.

答案9

得分: 0

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, ", "))
}

英文:

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, ", "))
}

答案10

得分: 0

func crackU32s2Bytes(us []uint32) []byte {
	var bs []byte
	var ptrBs = (*reflect.SliceHeader)(unsafe.Pointer(&us))
	ptrBs.Len = len(us) * 4
	ptrBs.Cap = ptrBs.Len
	return bs
}

func crackBytes2U32s(bs []byte) []uint32 {
	var us []uint32
	var ptrBs = (*reflect.SliceHeader)(unsafe.Pointer(&bs))
	ptrUs.Len = len(bs) / 4
	ptrUs.Cap = ptrUs.Len
	return us
}
英文:
func crackU32s2Bytes(us []uint32) []byte {
	var bs []byte
	var ptrBs = (*reflect.SliceHeader)(unsafe.Pointer(&bs))
	var ptrUs = (*reflect.SliceHeader)(unsafe.Pointer(&us))
	ptrBs.Data = ptrUs.Data
	ptrBs.Len = ptrUs.Len*4
	ptrBs.Cap = ptrBs.Len
	return bs
}

func crackBytes2U32s(bs []byte) []uint32 {
	var us []uint32
	var ptrBs = (*reflect.SliceHeader)(unsafe.Pointer(&bs))
	var ptrUs = (*reflect.SliceHeader)(unsafe.Pointer(&us))
	ptrUs.Data = ptrBs.Data
	ptrUs.Len = ptrBs.Len/4
	ptrUs.Cap = ptrUs.Len
	return us
}

huangapple
  • 本文由 发表于 2012年8月13日 01:20:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/11924196.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定