Sorry, This post is Japanese Only.
I'm verifying the memory allocation of Go Launguage on WindowsPort using Windbg.
String type of Go Launguage is allocated using pointer (LongHex type) and Length (LongUnsignedHex type) .
まずはサンプルソース。
package main
import(
"fmt"
"bufio"
"os"
"unsafe"
)
// 構造体を定義
type H struct {
背番号 int
カナ byte
名前 string
住んでる所 string
性格 string
}
// main関数
func main(){
// 構造体をそれぞれ初期化
oHanaaruki := H{1,'o', "オオハナアルキ", "地上", "ちょっと凶暴"}
mHanaaruki := H{2,'m', "マンモスハナアルキ", "地上", "のんびり"}
dHanaaruki := H{3,'d', "ダンボハナアルキ", "空中", "ぱたぱた系"}
jHanaaruki := H{4,'j', "ジェットハナアルキ", "空中", "ビュンビュン系"}
bHanaaruki := H{5,'b', "バニラ・ランモドキ", "樹上", "お色気派"}
// 構造体のサイズを確認
fmt.Printf("構造体メモリサイズ:%d\n",unsafe.Sizeof(H{}))
// 構造体を配列に格納
hs := []H{oHanaaruki,mHanaaruki}
// 構造体のポインタを配列に格納
hsp := []*H{ &dHanaaruki, &jHanaaruki, &bHanaaruki}
// 配列のサイズを確認
fmt.Printf("構造体メモリ展開個数:%d\n",len(hs))
fmt.Printf("構造体ポインタ展開個数:%d\n",len(hsp))
// 構造体を初期化
h := new(H)
// 構造体へのポインタを初期化
var hp *H
hpt := new(*H)
// それぞれがどう割り当てられたかを確認
fmt.Printf("フツーの初期化:%d\n",h)
fmt.Printf("ポインタの初期化:%d\n",hp)
fmt.Printf("ポインタの初期化:%d\n",hpt)
fmt.Printf("構造体メモリアドレス:%p\n",&hs)
fmt.Printf("構造体ポインタアドレス:%p\n",hsp)
fmt.Printf("オオハナルキのポインタ:%p\n",&oHanaaruki)
fmt.Printf("ランモドキのポインタ:%p\n",&bHanaaruki)
fmt.Printf("ランモドキカナのポインタ:%p\n",&bHanaaruki.カナ)
fmt.Printf("ランモドキ名前のポインタ:%p\n",&bHanaaruki.名前)
fmt.Printf("ランモドキ住んでる所のポインタ:%p\n",&bHanaaruki.住んでる所)
fmt.Printf("ランモドキ性格のポインタ:%p\n",&bHanaaruki.性格)
ins := bufio.NewReader(os.Stdin)
for{
line, err := ins.ReadString('\n')
if err == os.EOF {
fmt.Printf(";;;End\n")
break
}
fmt.Printf(";;;%s",line)
}
}
構造体を定義して、それぞれを配列にしたり、初期化したり、その時にどんなふうに、メモリが配置されてるかを確認してみる。
動かすと
62行目移行で、Ctl+zが入力されるまでずっと待ってると言う動きになっているので、各変数をメモリ展開した後でWindbgを起動、このプロセスを捕まえて調査開始。
Menu(File) > Attach Process > Select main.exe
main.exe を探してAttach
Process And Threads Window を起動
メインプロセスには、このプログラムにはChannelは存在しないので、二つのスレッドが存在。
(There is One Process and Two Threads. Because this process has no Channel Type)
ここで次はMemoryWindowを起動して、
「ポインタの初期化:0x570018」のアドレスを探ると
アドレスの指す先は「0x0000」だけれども、確かに、領域が確保できてるのが判るのね。
それじゃ、中身のある、構造体配列はどうなってるんだろう。
「構造体メモリアドレス:0x5753f0」を探ってみると
そこにはまた、意味ありげなアドレスが。そこで、そのアドレスに飛んでみると、
むむ、これは?
ここで構造体の定義を考えてみると、int型、byte型、String型をそれぞれ持たせていたはず。
そこで、最後のPrintf結果から、それぞれのアドレスの差異を計算すると
ランモドキのポインタ:0x5737a0
ランモドキカナのポインタ:0x5737a4
ランモドキ名前のポインタ:0x5737a8
ランモドキ住んでる所のポインタ:0x5737b0
ランモドキ性格のポインタ:0x5737b8
int=(0x5737a4)-(0x5737a4)=4Byte、byte=(0x5737a4)-(0x5737a8)=4Byte、String=(0x5737a8)-(0x5737b0)=8Byte のサイズ、と考えることができる。だから構造体のトータルサイズは32Byte、と計算されてたんだぁ、と納得もできるよね。
ちなみに、今表示しているWindbgのMemoryWindowではLong Hex モードなので、一つの塊が4Byte、つまり、ここに確かに構造体の中身が展開されているのは間違いなさそう。
でもString型は文字列。どうして全部8Byteなんだろ? これは、どこかにStringの中身本体が展開されていて、ここにはそのアドレスだけが展開されているって考えられるので、早速、そのアドレスへ飛んでみる。
それっぽいよねぇ、文字列で「オオハナアルキ」は7文字。e3を区切りとして数えると7個あって、最初の二つは同じ値、5番目の値は、1番目の値の8Byte先の数字。2Byteと考えると、5番目を基準に、4個先の文字コードの文字が1番目にいるんだもの。これは、確定じゃね?
そこで、ちょっと一つ前に戻って、
再度、構造体本体のアドレスに戻って、今度は「Long Unsigned Hex」モードで表示。すると、今度は先ほどのString型を展開したアドレスの後ろに意味ありげな数字が。
Go言語ではString型の中身はすべてUnicodeであつかうという決まりがあるので、このアドレス位置から指定された数字の分だけ値をとりだして、空白に%を埋めて、URLデコードにかけてみると
「%e3%82%aa%e3%82%aa%e3%83%8f%e3%83%8a%e3%82%a2%e3%83%ab%e3%82%ad」→「オオハナアルキ」
というわけで、オオハナアルキを発見。
Go言語にはクラスの概念は無いけれども、ANSI-Cの時代から培われてきたメモリアロケートの考え方がしっかりと生きていて、全ての展開アドレスを自プロセスの中できちんと完結させられるように仕組みがなっている、と考えていいと思う。
結論:
「Go言語の初期化済みString型は、LongHexのメモリアドレスポインタとLongUnsignedHexのサイズで構成されている」