この記事について
Go言語の以下のポインター関連の型について簡単に整理します。
-
&
で取得するアドレス - uintptr
- unsafe.Pointer
&
で取得するアドレス
- 変数のアドレスを表す整数値
- 値は整数値だが、型はそれが指す型に応じて異なる
例
i := 1
s := []string{"x", "y"}
pi := &i // pi の型は *int
ps := &s // ps の型は *[]string
- 当然ながらアドレスを参照できる →
*pi
とする - しかし、C言語のようにポインターの演算はできない →
pi++
とか出来ない
uintptr
- Go言語の Builtin 型であり、アドレスを格納できる大きさを持つ「整数型」
- 単なる整数型なので、アドレスを参照することはできない →
*pi
が出来ない - 単なる整数型なので、演算ができる →
pi++
とかが出来る。- ただしC言語とは異なり単なる整数演算になる。
pi++
は値に1を足すだけで、次のオブジェクトを指すアドレスにインクリメントするのではない
- ただしC言語とは異なり単なる整数演算になる。
- uintptr がオブジェクトのアドレスを指していても、そのオブジェクトは GC で回収されうる
- GC は、uintptr をオブジェクトを参照してるものと見なさないため
- 後述の注意事項も参照
unsafe.Pointer
- すべての型のポインターを表せる型。C言語の
void *
という感じ - 以下の特性を持つ
-
任意の型のポインター
からunsafe.Pointer
へ変換可能。またその逆も可能。 -
uintptr
からunsafe.Pointer
へ変換可能。またその逆も可能。
-
- Go の型管理の機構を迂回して任意のアドレスを読み書きてしまうため、危険であり通常使うことはない
注意
- uintptr と unsafe.Pointer の変換の間に GC が走る余地を入れないこと
- unsafeパッケージの説明 にあるように、uintptr をいったん変数に保持せず1つの式で変換を行う
これは大丈夫
p = unsafe.Pointer(uintptr(p) + offset)
これは危険
// 1行目と2行目の間に、p が GC で回収されるかも。
u := uintptr(p)
p = unsafe.Pointer(u + offset)
サンプル
実用性は無いが、int32 のスライスを C言語のように処理して合計値を求める例。
package main
import (
"fmt"
"unsafe"
)
func sum(ptr *int32, size int) int32 {
var sum int32
p := unsafe.Pointer(ptr)
for i := 0; i < size; i++ {
// 4バイト(32ビット)、ポインターを進める
sum += *(*int32)(unsafe.Pointer(uintptr(p) + uintptr(i*4)))
}
return sum
}
func main() {
s := []int32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// sum には、スライスのアドレス &s ではなく、スライスが指してる配列の
// 先頭アドレス &s[0] を渡す必要あり
fmt.Printf("Sum = %d\n", sum(&s[0], len(s)))
}
実行結果: Sum = 55