web-dev-qa-db-ja.com

異なるタイプのスライス間で変換する

バイトスライス([]byte)UDPソケットから取得し、整数スライス([]int32)基になる配列を変更せずに、またはその逆。 C(++)では、ポインター型間でキャストするだけです。 Goでこれをどのように行うのですか?

40
slartibartfast

他の人が言ったように、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))
38

簡単な答えは、あなたはできないということです。あるタイプのスライスを別のタイプのスライスにキャストすることはできません。配列をループして、配列内の各項目をキャストしながら、希望するタイプの別の配列を作成します。型安全性はgoの重要な機能であるため、これは一般的に良いことと見なされています。

8
Jeremy Wall

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つずつ読み取り、必要なものをすべて作成することです。

アレックス

5
alex

サイズが不明な問題があり、次のコードで以前の安全でない方法を調整しました。バイトスライスbが与えられると...

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

配列からスライスへの例には、配列が割り当てられていないため、架空の大きな定数とスライス境界が同じ方法で使用されます。

2
Tony Wilson

「安全でない」パッケージでそれを行うことができます

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

おそらく、以前の回答が与えられたときには利用できなかったかもしれませんが、binary.Readメソッドは、上記の「正しい方法」よりも良い答えです。

このメソッドを使用すると、リーダーからバイナリデータを目的のタイプの値またはバッファに直接読み込むことができます。これを行うには、バイト配列バッファー上にリーダーを作成します。または、バイト配列を提供しているコードを制御できる場合は、それを置き換えて、中間バイト配列を必要とせずにバッファに直接読み込むことができます。

ドキュメントと素敵な小さな例については、 https://golang.org/pkg/encoding/binary/#Read を参照してください。

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