関連記事
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。前回の続きで unsafe.Slice と unsafe.SliceData の使い方について。
unsafeパッケージって、Goの安全性の枠から外れる操作をするパッケージなので、基本的に使わないほうが良いものですが、知ってると便利なパッケージでもあります。
cgoを使う場合はほぼ必須です。特に unsafe.Pointer はめっちゃ使います。
unsafe.Pointer は、C言語でいう (void *) と同じものとなるので、これを利用することで任意の型にキャストできます。
unsafe.Pointer については、以前の記事でサンプル作ったので、そちらご参照ください。
今回は、*byteと[]byteの相互変換について。主に低レベルなAPI操作やcgoなどで利用したりします。
unsafe.SliceData は、特定のスライス []T を *T に変換してくれます。これはGoのヒープメモリを指すポインタとなります。
unsafe.Slice は、逆を行うものです。つまり、*T を []T にしてくれる関数。ポインタとサイズを渡すとスライスにして返してくれます。
こちらは元のデータが指すポインタをそのまま指した状態で返してくれます。つまり、C側のポインタを渡してスライスにした場合はC側のスタックメモリをそのまま指している状態となります。
元のデータのポインタを直接指しているので、C側から受け取った char * なんかを unsafe.Slice を使ってスライスにして、それを変更などするとC側のスタックメモリを直接変更することになります。非常に危険です。データを見る分には問題ありません。
サンプル
ちょっと複数のファイルに跨ったサンプルとなっているのですが、以下のようなことをしています。
- Goのmain関数からcgo経由でCの関数を呼び出し (main.go)
- Cの関数からcgoでエクスポートしているGoの関数を呼び出し (c.go)
- Goの関数でC側から受け取ったデータを変更して、更にCの関数に渡す (export.go)
- 最後に呼ばれるCの関数で変更された値を表示 (c.go)
Go -> C -> Go -> C とやり取りしている感じです。
サンプル自体は、以下にアップしてありまして、そこにあるREADME.mdに詳しく書いていますので、よかったらご参照ください。
try-golang/examples/singleapp/unsafe_slice_with_cgo at main · devlights/try-golang · GitHub
main.go
package main /* extern void c_func(); */ import "C" func main() { C.c_func() }
c.go
package main /* #include <stdio.h> #include <string.h> extern void go_func(const char *s, size_t n); void c_func() { char s[] = "helloworld"; size_t s_size = sizeof(s); go_func(s, s_size); } void c_func2(const char *s, size_t n) { char buf[n]; { memcpy(buf, s, n); buf[n-1] = '\0'; } printf("[C ] %s\n", buf); } */ import "C"
export.go
package main /* extern void c_func2(const char *s, size_t n); */ import "C" import ( "fmt" "unsafe" ) //export go_func func go_func(s *C.char, n C.size_t) { var ( sPtr = unsafe.Pointer(s) cSlice = unsafe.Slice((*byte)(sPtr), n) // cSliceはC側のスタック変数を指している ) fmt.Printf("[Go] %s\n", cSlice) // 何らかの変換を行う(例としてデータをリバース) // // 注意点として、cSliceはC側のスタック変数をそのまま指しているため // これを直接変更すると、C側のスタックメモリを書き換えてしまうことになる。 // 必ず、コピーを取ってから変更処理は行うこと。 // // また、C.GoBytes(), C.CString() を利用せずに直接C側のデータを扱っているので // cSliceの中は最後に終端文字が入った状態となっている。 // この状態でそのままスライスをリバースすると \0 が先頭に来ることになるので除去してから処理する。 var ( goSlice []byte // Go側で扱うスライス dataLen = int(n) // 実データのサイズ ) // NULL終端文字がある場合は減算して実データサイズとする if dataLen > 0 && cSlice[dataLen-1] == 0 { dataLen-- } // 実データ分をコピー goSlice = make([]byte, dataLen) copy(goSlice, cSlice[:dataLen]) // リバース for i, j := 0, len(goSlice)-1; i < j; i, j = i+1, j-1 { goSlice[i], goSlice[j] = goSlice[j], goSlice[i] } // 終端追加 goSlice = append(goSlice, 0) // C側の関数に渡すための準備 var ( bytePtr = unsafe.SliceData(goSlice) // *byteに変換し charPtr = (*C.char)(unsafe.Pointer(bytePtr)) // そこから (char *) に変換 charLen = C.size_t(len(goSlice)) // サイズはスライスからそのまま取得 ([]byteの場合はこれでOK) ) C.c_func2(charPtr, charLen) }
実行
$ task
task: [default] go run *.go
[Go] helloworld
[C ] dlrowolleh
参考情報
個人的Goのおすすめ書籍
個人的に読んでとても勉強になった書籍さんたちです。
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。






