map系の型は後述の内部定義された構造体型として扱われます。
なので型宣言されただけで構造体のメモリは確保されます(スタックかヒープかは状況による)。
以下の&m3
はその確保された構造体のアドレスを表示していることになります。
go
1var m3 map[string]int
2fmt.Printf("address: %p\n", &m3)
map型の内部表現
https://github.com/golang/go/blob/2580d0e08d5e9f979b943758d3c49877fb2324cb/src/runtime/map.go#L116-L130
go
1type hmap struct {
2 // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
3 // Make sure this stays in sync with the compiler's definition.
4 count int // # live cells == size of map. Must be first (used by len() builtin)
5 flags uint8
6 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
7 noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
8 hash0 uint32 // hash seed
9
10 buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
11 oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
12 nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
13
14 extra *mapextra // optional fields
15}
型宣言だけだと、buckets==nilです。bucketsポインタの指す先に別のメモリがあてがわれて初めてmap型として機能します。
「bucketsポインタの指す先に別のメモリをあてる」のがmakeやリテラルなどを記述する方法です。
go
1m1 := map[string]int{}
2m2 := make(map[string]int)
(ちなみにm1とm2は初期化の記述方法が違いますが得られるものは同等です。)
m1やm2であればマップが機能するためのヒープメモリ割り当てがkey、elem、bucketポインタにセットされているのでマップとしてアクセスすることができます。
mapの宣言と文字列の宣言が違うのか?といわれるとメモリ構造は似ています。
https://github.com/golang/go/blob/2580d0e08d5e9f979b943758d3c49877fb2324cb/src/runtime/string.go#L238-L241
go
1type stringStruct struct {
2 str unsafe.Pointer
3 len int
4}
strポインタの指す先に文字列データが格納されている必要がありますが、こちらは「文字列値」そのものがコード中にあるのでそれをコンパイラはプログラムコードに埋め込み、そこへのポインタや長さが最初から算出可能なのでstrもlenもコンパイル時点で初期値を与えられます。
つまり、文字列もスライスもマップも宣言に関する情報を持つ構造体があり、実データは別途メモリ確保してそのポインタを構造体が持っているという形です。
実データ用に別途初期化処理にてメモリ割り当てが必要です。それを行うのがmake関数やリテラル初期化による記述ということです。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。