Go ではポインタは扱えるけれど、ポインタ演算は扱えないというのが共通認識でした。もちろん unsafe.Pointer
と uintptr
を使う事で、出来なくはなかったのですが簡単ではありませんでした。
package main
import (
"unsafe"
)
type foo struct {
k int64
v int64
}
func main() {
f := &foo{3,4}
// unsafe.Pointer() で匿名ポインタにして
// uintptr() で演算可能にして
// +8 バイト(64bit)足して
// unsafe.Pointer で匿名ポインタに戻して
// そこにはフィールド v があるはずなので *int64 にキャストして
// デリファレンスすれば出来上がり
*(*int64)(unsafe.Pointer((uintptr(unsafe.Pointer(f))+8))) = 5 // グヒヒ
println(f.v) // 5
}
Go 1.17 からは unsafe.Add
と unsafe.Slice
が導入される事で、少しだけ簡単にポインタ演算そしてポインタからスライスを復元する事ができる様になります。(参照)
package main
import (
"fmt"
"unsafe"
)
func main() {
b := []byte{1, 2, 3, 4, 5}
pb := &b[3]
fmt.Println(unsafe.Slice(pb, 2)) // 4, 5
// sizeof(byte) 分ポインタをずらす
pb = (*byte)(unsafe.Add(unsafe.Pointer(pb), 1))
fmt.Println(*pb) // 5
v := []int{2, 3, 4, 5, 6}
pi := &v[3]
// オフセット3からのスライスを得る
fmt.Println(unsafe.Slice(pi, 2)) // 5, 6
// sizeof(int) 分ポインタをずらす
pi = (*int)(unsafe.Add(unsafe.Pointer(pi), 8))
fmt.Println(*pi) // 6
}
もちろん不正なオフセットを指定して unsafe.Add
を呼び出してデリファレンスしたり、不正なサイズを指定してスライスを復元したりすると panic する事になります。