UDPソケットからバイトスライス([]byte
)を取得し、基になる配列を変更せずに整数スライス([]int32
)として扱いたい、またはその逆。C(++)では、ポインタ型間でキャストするだけです。Goでこれをどのように行うのですか?
9 に答える
他の人が言っているように、ポインタをキャストすることは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の別の実装では失敗する可能性があるため、これはお勧めできません。言語仕様にないものを想定しています。ただし、これは完全なコピーを行いません。このコードは、安全でないものを使用して、すべてのスライスに共通の「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))
1つの例外を除いて、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のバイトは、コンピューターによってレイアウトが異なる可能性があるため、必要に応じてこれらがレイアウトであると想定しないでください。
したがって、最も安全なアプローチは、バイトの配列を1つずつ読み取り、それらから必要なものを作成することです。
アレックス
簡単な答えはあなたができないということです。あるタイプのスライスを別のタイプのスライスにキャストすることはできません。配列をループして、配列内の各アイテムをキャストしながら、必要なタイプの別の配列を作成します。型安全性はgoの重要な機能であるため、これは一般的に良いことと見なされています。
サイズが不明な問題があり、以前の安全でないメソッドを次のコードで微調整しました。与えられたバイトスライスb..。
int32 slice is (*(*[]int)(Pointer(&b)))[:len(b)/4]
配列からスライスへの例には、架空の大きな定数が与えられ、配列が割り当てられていないため、同じ方法でスライス境界が使用されます。
あなたは「安全でない」パッケージでそれをすることができます
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
*/
以前の回答が出されたときには利用できなかったかもしれませんが、このbinary.Read
方法は上記の「正しい方法」よりも優れた回答であるように思われます。
この方法では、バイナリデータをリーダーから目的のタイプの値またはバッファに直接読み取ることができます。これを行うには、バイト配列バッファー上にリーダーを作成します。または、バイト配列を提供するコードを制御できる場合は、暫定バイト配列を必要とせずに、コードを置き換えてバッファーに直接読み込むことができます。
ドキュメントとちょっとした例については、https://golang.org/pkg/encoding/binary/#Readを参照してください。
unsafe
Go 1.17以降、パッケージを使用してこれを行うためのより簡単な方法があります。
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)
}
1.17以降に移動
Go 1.17は、まさにこれを行う関数を導入しました。unsafe.Slice
[]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)
}
}
遊び場。
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, ", "))
}