Go言語  スライス・string・interface{}の内部構造をプログラミングによって調べてみた

# 概要

スライス・string・interface{}の内部構造をプログラミングによって調査していきます。その準備として変数の型の再定義の方法を考えます。最後にGoのソースコードを調べたものも載せました。

# 環境

```
$ go version
go version go1.12 windows/amd64
```

# 準備

## 変数が占めているメモリサイズの取得

`unsafe.Sizeof`を使います


```go:main.go
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var sliceVal []int
	var stringVal string
	var interfaceVal interface{}
	fmt.Printf("sliceVal:%d, stringVal:%d, interfaceVal:%d\n",
		unsafe.Sizeof(sliceVal), unsafe.Sizeof(stringVal), unsafe.Sizeof(interfaceVal))
}
```

実行結果

```
$ go run main.go
sliceVal:24, stringVal:16, interfaceVal:16
```

## 変数が占めている領域を定義と違う型で再定義する

`var num uint = 0x0102030405060708`を`[8]uint8`で再定義して表示を試みます。

```go:main.go
package main

import (
	"fmt"
)

func main() {
	var num uint = 0x0102030405060708
	reNum := (*[8]uint8)(&num)
	fmt.Println(*reNum)
}
```

```
$ go run main.go
# command-line-arguments
.\main.go:9:22: cannot convert &num (type *uint) to type *[8]uint8
```

このままでは型変換できないようです。そこで使うのが`unsafe.Pointer`です。

```go:main.go
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var num uint = 0x0102030405060708
	reNum := (*[8]uint8)(unsafe.Pointer(&num))
	fmt.Println(*reNum)
}
```
```
$ go run main.go
[8 7 6 5 4 3 2 1]
```

無事表示できました。数字の順番が`num`と逆になっているのはCPUがIntel製でリトルエンディアンのためです。
これでも十分ですが、構造体を使った方が後々便利です。さらに`fmt.Printf`に変更しています。

```go:main.go
package main

import (
	"fmt"
	"unsafe"
)

type memory struct {
	field1 [4]uint8
	field2 [2]uint16
}

func main() {
	var num uint = 0x0102030405060708
	reNum := (*memory)(unsafe.Pointer(&num))
	fmt.Printf("%#v\n", *reNum)
}
```
```
$ go run main.go
main.memory{field1:[4]uint8{0x8, 0x7, 0x6, 0x5}, field2:[2]uint16{0x304, 0x102}}
```


# スライスの調査

スライスは24バイトなので、とりあえず`uint`型3個として中身を見てみます。

```go:main.go
package main

import (
	"fmt"
	"unsafe"
)

type memory struct {
	field1 uint
	field2 uint
	field3 uint
}

func dumpMemory(s []int) {
	p := (*memory)(unsafe.Pointer(&s))
	fmt.Printf("%+v\n", *p)
	fmt.Printf("len: %d, cap: %d\n", len(s), cap(s))
}
func main() {
	var sliceVal []int
	sliceVal = make([]int, 1, 2)
	sliceVal[0] = 12
	dumpMemory(sliceVal)
	sliceVal = append(sliceVal, 34)
	dumpMemory(sliceVal)
	sliceVal = append(sliceVal, 56)
	dumpMemory(sliceVal)
	sliceVal = append(sliceVal, 78)
	dumpMemory(sliceVal)
	fmt.Println(sliceVal)
}
```

```
$ go run main.go
{field1:824633762016 field2:1 field3:2}
len: 1, cap: 2
{field1:824633762016 field2:2 field3:2}
len: 2, cap: 2
{field1:824633795808 field2:3 field3:4}
len: 3, cap: 4
{field1:824633795808 field2:4 field3:4}
len: 4, cap: 4
[12 34 56 78]
```

結果を見てみると`field2`が要素数(len)で、`field3`が容量(cap)であることがわかる。`field1`はアドレスっぽいです。容量(cap)が変わらないときは`field1`の値も変わらないようです。つまり容量が増えると配列用のメモリを新たに割り当てていると推測されます。次に`field1`を`uint`型の配列ポインタとして中身を見てみます。プログラムは`memory`と`dumpMemory`のみ変更します。

```go:main.go
type memory struct {
	field1 *[10]uint
	field2 uint
	field3 uint
}

func dumpMemory(s []int) {
	p := (*memory)(unsafe.Pointer(&s))
	fmt.Printf("field1: %v\n%+v\n", *p.field1, *p)
	fmt.Printf("len: %d, cap: %d\n", len(s), cap(s))
	fmt.Println()
}
```

```
$ go run main.go
field1: [12 0 0 0 0 0 0 0 0 0]
{field1:0xc000066080 field2:1 field3:2}
len: 1, cap: 2

field1: [12 34 2322222865247988070 0 2322222865247988070 2319389199166484827 1 2 7811852430209544791 101]
{field1:0xc000066080 field2:2 field3:2}
len: 2, cap: 2

field1: [12 34 56 0 0 0 0 0 0 0]
{field1:0xc0000640e0 field2:3 field3:4}
len: 3, cap: 4

field1: [12 34 56 78 0 0 0 0 0 0]
{field1:0xc0000640e0 field2:4 field3:4}
len: 4, cap: 4

[12 34 56 78]
```

`34`を追加したとき`field1`のアドレスが変わっていないのに`field1[2]`以降が変わっていますが、スライスで用意した領域は`field1[1]`までなので、他の処理で使われたのだと思います。

結果、スライスは次のような内部構造になっているようです。

|相対アドレス|メモリの内容|
|-------|----|
| 0～7バイト目 |実データの配列があるメモリのポインタ|
| 8～15バイト目 |要素数|
| 16～23バイト目|容量|


# stringの調査

`string`のメモリサイズは16バイトなので、`uint`型2個として中身を見てみます。

```go:main.go
package main

import (
	"fmt"
	"unsafe"
)

type memory struct {
	field1 uint
	field2 uint
}

func dumpMemory(s string) {
	p := (*memory)(unsafe.Pointer(&s))
	fmt.Printf("%+v\n", *p)
}
func main() {
	dumpMemory("abcde")
	dumpMemory("あいうえお")
}
```

```
$ go run main.go
{field1:5003377 field2:5}
{field1:5009736 field2:15}
```

結果を見ると、`field1`はアドレスのようで、`field2`は文字数ではなくバイト数のように思われます。そこで、以下のURLを参照してUTF-8の文字コードを調べてみました。ひらがなは3バイトなので`field2`の値と一致します。

http://orange-factory.com/dnf/utf-8.html

|文字|UTF-8文字コード|
|---|-------|
|a|61|
|b|62|
|c|63|
|d|64|
|e|65|
|あ|E38182|
|い|E38184|
|う|E38186|
|え|E38188|
|お|E3818A|

そこで、`field1`をbyteの配列として中身を表示してみます。プログラムは`memory`と`dumpMemory`のみ変更します。

```go:main.go
type memory struct {
	field1 *[20]byte
	field2 uint
}

func dumpMemory(s string) {
	p := (*memory)(unsafe.Pointer(&s))
	fmt.Printf("%+v\n", *p)
	for i := uint(0); i < p.field2; i++ {
		fmt.Printf("%d: %x\n", i, p.field1[i])
	}
	fmt.Println()
}
```
```
$ go run main.go
{field1:0x4c69d1 field2:5}
0: 61
1: 62
2: 63
3: 64
4: 65

{field1:0x4c82af field2:15}
0: e3
1: 81
2: 82
3: e3
4: 81
5: 84
6: e3
7: 81
8: 86
9: e3
10: 81
11: 88
12: e3
13: 81
14: 8a
```
調べたUTF-8のコードと一致しましたので`field1`は`byte`配列のポインタで間違いないようです。

結果、stringは次のような内部構造になっているようです。

|相対アドレス|メモリの内容|
|-------|----|
| 0～7バイト目 |実データのbyte配列があるメモリのポインタ|
| 8～15バイト目 |byte数|

内部構造が分かったところでstringはイミュータブルで変更不可ですが、無理やり変更してみたくなります。次のような先頭の英文字を変更するプログラムを試みましたがpanicが発生しました。

```go:main.go
package main

import (
	"fmt"
	"unsafe"
)

type memory struct {
	field1 *[20]byte
	field2 uint
}

func main() {
	str := "abcde"
	p := (*memory)(unsafe.Pointer(&str))
	fmt.Printf("str: %p, fiedl1: %p\n", &str, p.field1)
	fmt.Println(str, "\n*************************")
	p.field1[0] = '0'
	fmt.Println(str)
}
```

実行結果

```
$ go run main.go
str: 0xc0000461d0, fiedl1: 0x4c46ec
abcde
*************************
unexpected fault address 0x4c46ec
fatal error: fault
[signal 0xc0000005 code=0x1 addr=0x4c46ec pc=0x491c76]

以下省略
```

この結果を見ると`str`のアドレスと`abcde`が格納されているアドレス(`field1`)が随分違うことに気づきます。よって`abcde`が格納されているメモリは書き込み不可属性ではないかと推測します。そこで`str`と同じようなアドレスに割り当てられるようにして、再び変更を試みます。 `main()`のみ記載します。

```go:main.go
func main() {
	str0 := "abcd"
	str := str0 + "e"
	p := (*memory)(unsafe.Pointer(&str))
	fmt.Printf("str: %p, fiedl1: %p\n", &str, p.field1)
	fmt.Println(str, "\n*************************")
	p.field1[0] = '0'
	fmt.Println(str)
}
```

```
$ go run main.go
str: 0xc00005c1c0, fiedl1: 0xc000070080
abcde
*************************
0bcde
```

`field1`のアドレスが書き込み可能なところに割り当てられたため変更が可能になったようです。

# interface{}の調査

メモリサイズは16バイトなので2個の`uint`としてメモリダンプします。中に入れるデータとしてもダンプ時に分かるように`int`と同類にします。


```go:main.go
package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

type memory struct {
	field1 uint
	field2 uint
}

func dumpMemory(val interface{}) {
	p := (*memory)(unsafe.Pointer(&val))
	fmt.Printf("%+v\n", *p)
	fmt.Printf("type: %+v\n", reflect.TypeOf(val))
}

func main() {
	dumpMemory(123456)
}
```

```
$ go run main.go
{field1:4863776 field2:5102552}
type: int
```
残念ながら`123456`は出てきませんでした。データが入っていないときはポインタであると考えます。そこで`*[4]uint`型にしてポインタ先のメモリを4個分づつダンプします。

```go
type memory struct {
	field1 *[4]uint
	field2 *[4]uint
}

func dumpMemory(val interface{}) {
	p := (*memory)(unsafe.Pointer(&val))
	fmt.Printf("field1: %+v, field2: %+v\n", *p.field1, *p.field2)
	fmt.Printf("type: %+v\n", reflect.TypeOf(val))
}
```
```
$ go run main.go
field1: [8 0 9369747855051551226 5672240], field2: [123456 536870951 1 4376800]
type: int
```

`field2`の先頭に期待していた数値がでましたので`field2`が実データのポインタでしょう。`val`に入れるデータを変えてもう少し確かめます。 `main()`のところで `dumpMemory([2]uint{123456, 7890})`としてみます。

```
$ go run main.go
field1: [16 0 10450611741568835945 5672256], field2: [123456 7890 0 0]
type: [2]uint
```

`field2`は期待通りでした。`field1`の先頭は`8`から`16`になっているのでデータバイト数ではないかと推測できます。

以前、投稿した記事[Go言語 構造体をfmt.Printfで表示するときの書式の不思議](https://qiita.com/h6591/items/66f4da8cf422087966c5)で`reflect.TypeOf`を`%#v`で表示したときの構造かも知れないので、`reflect.TypeOf`を`%#v`と`%f`とし`field1`も`%#v`にします。

```go
func dumpMemory(val interface{}) {
	p := (*memory)(unsafe.Pointer(&val))
	fmt.Printf("field1: %#v, field2: %+v\n", *p.field1, *p.field2)
	fmt.Printf("type: %#v\n    : %f\n", reflect.TypeOf(val), reflect.TypeOf(val))
}
```
```
$ go run main.go
field1: [4]uint{0x10, 0x0, 0x910808025dbca169, 0x568d40}, field2: [123456 7890 0 0]
type: &reflect.rtype{size:0x10, ptrdata:0x0, hash:0x5dbca169, tflag:0x2, align:0x8, fieldAlign:0x8, kind:0x91, alg:(*reflect.typeAlg)(0x568d40), gcdata:(*uint8)(0x4dcb48), str:8166, ptrToThis:0}
    : &{%!f(uintptr=16) %!f(uintptr=0) %!f(uint32=1572643177) %!f(reflect.tflag=2) %!f(uint8=8) %!f(uint8=8) %!f(uint8=145) %!f(*reflect.typeAlg=&{0x402720 0x402f20}) %!f(*uint8=0x4dcb48) %!f(reflect.nameOff=8166) %!f(reflect.typeOff=0)}
```

`field1`と`type`を較べてみると４個中3個(`size,ptrdata,alg`)が一致しています。そこで`field1`は`type`であると考えれらます。また`field1`の`0x910808025dbca169`が`type`では5個のフィールドになっています。64ビットを5個に分けるには`16,16,16,8,8`または`32,8,8,8,8`が考えられる。`hash`が`uint32`、必然的に`32,8,8,8,8`の構成になる。`alg`が二つの項目を持っていて`%f`で16進表示されているのでポインタと考えられます。また`%#v`のフォーマットで`str`と`ptrToThis`が`%#v`で10進表示になっているのは符号付きだからだと思います。`rtype`は直接使用できないので、自分で11個の項目`myType`を定義してみます。

```go:main.go
package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

type memory struct {
	field1 *myType
	field2 *[4]uint
}

type myType struct {
	size       uintptr
	ptrdata    uintptr
	hash       uint32
	tflag      uint8
	align      uint8
	fieldAlign uint8
	kind       uint8
	alg        *[2]*uint
	gcdata     *uint8
	str        int
	ptrToThis  int
}

func dumpMemory(val interface{}) {
	p := (*memory)(unsafe.Pointer(&val))
	fmt.Printf("field1: %#v\n     : %f , field2: %+v\n", *p.field1, *p.field1, *p.field2)
	fmt.Printf("type: %#v\n    : %f\n", reflect.TypeOf(val), reflect.TypeOf(val))
}

func main() {
	dumpMemory([2]uint{123456, 67890})
}
```

```
$ go run main.go
field1: main.myType{size:0x10, ptrdata:0x0, hash:0x5dbca169, tflag:0x2, align:0x8, fieldAlign:0x8, kind:0x91, alg:(*[2]*uint)(0x569d40), gcdata:(*uint8)(0x4ddeb8), str:8190, ptrToThis:4870240}
     : {%!f(uintptr=16) %!f(uintptr=0) %!f(uint32=1572643177) %!f(uint8=2) %!f(uint8=8) %!f(uint8=8) %!f(uint8=145) %!f(*[2]*uint=&[0x4cf668 0x4cf638]) %!f(*uint8=0x4ddeb8) %!f(int=8190) %!f(int=4870240)} , field2: [123456 67890 0 0]
type: &reflect.rtype{size:0x10, ptrdata:0x0, hash:0x5dbca169, tflag:0x2, align:0x8, fieldAlign:0x8, kind:0x91, alg:(*reflect.typeAlg)(0x569d40), gcdata:(*uint8)(0x4ddeb8), str:8190, ptrToThis:0}
    : &{%!f(uintptr=16) %!f(uintptr=0) %!f(uint32=1572643177) %!f(reflect.tflag=2) %!f(uint8=8) %!f(uint8=8) %!f(uint8=145) %!f(*reflect.typeAlg=&{0x402720 0x402f20}) %!f(*uint8=0x4ddeb8) %!f(reflect.nameOff=8190) %!f(reflect.typeOff=0)}
```

`alg`の二つのポインタが違っています。ポインタだと思うのでデータポインタでなく関数のポインタにしてみます。また、`ptrToThis`の値が違うので`str`と`ptrToThis`を`int32`にしてみます。

```go
type myType struct {
	size       uintptr
	ptrdata    uintptr
	hash       uint32
	tflag      uint8
	align      uint8
	fieldAlign uint8
	kind       uint8
	alg        *[2]func()
	gcdata     *uint8
	str        int32
	ptrToThis  int32
}
```
```
$ go run main.go
field1: main.myType{size:0x10, ptrdata:0x0, hash:0x5dbca169, tflag:0x2, align:0x8, fieldAlign:0x8, kind:0x91, alg:(*[2]func())(0x569d40), gcdata:(*uint8)(0x4ddeb8), str:8190, ptrToThis:0}
     : {%!f(uintptr=16) %!f(uintptr=0) %!f(uint32=1572643177) %!f(uint8=2) %!f(uint8=8) %!f(uint8=8) %!f(uint8=145) %!f(*[2]func()=&[0x402720 0x402f20]) %!f(*uint8=0x4ddeb8) %!f(int32=8190) %!f(int32=0)} , field2: [123456 67890 0 0]
type: &reflect.rtype{size:0x10, ptrdata:0x0, hash:0x5dbca169, tflag:0x2, align:0x8, fieldAlign:0x8, kind:0x91, alg:(*reflect.typeAlg)(0x569d40), gcdata:(*uint8)(0x4ddeb8), str:8190, ptrToThis:0}
    : &{%!f(uintptr=16) %!f(uintptr=0) %!f(uint32=1572643177) %!f(reflect.tflag=2) %!f(uint8=8) %!f(uint8=8) %!f(uint8=145) %!f(*reflect.typeAlg=&{0x402720 0x402f20}) %!f(*uint8=0x4ddeb8) %!f(reflect.nameOff=8190) %!f(reflect.typeOff=0)}
```

ここで完全に一致しました。`alg`が関数のポインタとは分かりましたが、関数の引数や戻り値の型などまでは分かりません。私がプログラミングで調べられるのはここまでです。
結論として`field1`は`Type`の構造体のポインタで`Type`の先頭項目はデータのバイト数です。`field2`は実データがある領域のポインタです。最後のほうは`reflect.rtype`の構造の調査になってしまいました。


|相対アドレス|メモリの内容|
|-------|----|
| 0～7バイト目 |データのタイプ情報(reflect.rtype)のポインタ|
| 8～15バイト目 |実データが格納されているポインタ|


# Appendix


## Goソースを調べてみた

### string とスライス

スライスとstringの内部構造についてGo言語のソースを探してみましたら、それらしいのが見つかりました。ソース名は`reflect/value.go`です。

```go:reflect/value.go
// StringHeader is the runtime representation of a string.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type StringHeader struct {
	Data uintptr
	Len  int
}

// stringHeader is a safe version of StringHeader used within this package.
type stringHeader struct {
	Data unsafe.Pointer
	Len  int
}

// SliceHeader is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

// sliceHeader is a safe version of SliceHeader used within this package.
type sliceHeader struct {
	Data unsafe.Pointer
	Len  int
	Cap  int
}
```

###  interface{}

Go言語のソースから`reflect/value.go`と`reflect/type.go`に下記のような構造体を見つけました。
`memory`が`emptyInterface`に対応して、 `myType`が`rtype`に対応しているようです。

```go:reflect/value.go
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
    typ  *rtype
    word unsafe.Pointer
}
```
```go:reflect/type.go
// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
    size       uintptr
    ptrdata    uintptr  // number of bytes in the type that can contain pointers
    hash       uint32   // hash of type; avoids computation in hash tables
    tflag      tflag    // extra type information flags
    align      uint8    // alignment of variable with this type
    fieldAlign uint8    // alignment of struct field with this type
    kind       uint8    // enumeration for C
    alg        *typeAlg // algorithm table
    gcdata     *byte    // garbage collection data
    str        nameOff  // string form
    ptrToThis  typeOff  // type for pointer to this type, may be zero
}
```

`tflag`は`uint8`で、`nameOff`と`typeOff`は`int32`です。