私が参加しているプログラミング教室で、自作ゲームを作成することができた生徒がプログラミングの深いところに触れてみたいということで、一緒にコンパイラを見ていくことにした。

言語はCやC++では大変なので、Go言語(golang)で作ることにしたが、データ構造の配列の箇所でC程気軽にポインタを扱えないことがわかった。


やりたいことは、

package main

import "fmt"

func main() {
	var arr [5]int
	p := &arr[0]
	*p = 1
	fmt.Println(p)
	fmt.Println(arr)

	p += 0x8 //int分だけポインタを移動する
	*p = 2 //配列の1番目に直接値を挿入する
	fmt.Println(arr)
}

上記のコードのように配列の0番目の値のポインタを取得して、int分のバイトを加算して配列の1番目の箇所に直接値を入れるということだけれども、Go言語ではこの手のポインタの操作は禁止されているらしい。


ちなみに上記のコードを実行すると、

# command-line-arguments
./main.go:12:7: cannot convert 0x8 (untyped int constant 8) to *int

が出力された。


期待した出力結果は

0x4000118030
[1 0 0 0 0]
[1 2 0 0 0]

※一行目の0xから始まる16進数の数字は実行する度に出力内容が異なる

UARTについてを知る1


ただ、絶対に禁止というわけではなく、unsafeパッケージを利用すれば、上記の内容を実現することができるらしい。

unsafe package - unsafe - Go Packages




早速、unsafeパッケージで書き直してみた。

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var arr [5]int

	//配列の1番目のポインタを出力しておく
	fmt.Println(&arr[0])

	//配列の0番目のポインタをunsafe経由で取得する
	p := uintptr(unsafe.Pointer(&arr[0]))

	/** int分だけポインタを移動する **/

	// 上の配列の第一引数までの距離を算出するために仮に定義した変数
	var i int

	// intのサイズ分移動したポインタを変数に代入する
	pp := (*int)(unsafe.Pointer(p + uintptr(unsafe.Sizeof(i))))

	//配列の1番目のポインタと上記で演算したポインタが一致しているか?
	fmt.Println(&arr[1])
	fmt.Println(pp)

	// 配列の1番目の箇所に直接値を挿入してみる
	*pp = 2
	fmt.Println(arr)
}

実行結果は

0x40000aa030
0x40000aa038
0x40000aa038
[0 2 0 0 0]

1行目の配列の0番目のポインタと3行目のunsafe経由でポインタを移動した後の値が一致し、配列に直接挿入した値も意図通りの結果になっている。

とりあえず、このコードがあれば、配列の理解は少しは進むかなと。


関連記事

プログラミング教育で注目すべきはARM + Debian + Pythonであるはずだ