Goのデータ型って実際何バイト？サンプルコードでメモリサイズを確認する

## はじめに

Goを使っていて、「この変数って、実際にはどれくらいメモリを使っているんだろう？」と疑問を感じたことはありませんか？

この記事ではGoの各データ型が実際にどのくらいのメモリ領域を確保するのか、具体的なサンプルコードと実行結果を交えて検証していきます。

型サイズを把握しておくことで、不要なメモリ消費を抑え、より効率的なコードが書けるようになるはずです。

なお、この記事におけるGoのバージョンは**go version go1.24.1 darwin/amd64**です。

## 【Go】データ型別に確保されるメモリ領域まとめ
Goでは格納する値の内容に関わらず、型ごとに確保されるメモリ領域が決まっています。

ここでは、`unsafe`パッケージの`Sizeof`関数を使って各型のサイズを検証していきましょう。

### プリミティブ型

まずは基本的なデータ型（プリミティブ型）から見ていきます。

```go:main.go
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	fmt.Printf("byte: %dバイト\n", unsafe.Sizeof(byte(0)))
	fmt.Printf("uint: %dバイト\n", unsafe.Sizeof(uint(0)))
	fmt.Printf("uint8: %dバイト\n", unsafe.Sizeof(uint8(0)))
	fmt.Printf("uint16: %dバイト\n", unsafe.Sizeof(uint16(0)))
	fmt.Printf("uint32: %dバイト\n", unsafe.Sizeof(uint32(0)))
	fmt.Printf("uint64: %dバイト\n", unsafe.Sizeof(uint64(0)))
	fmt.Printf("uintptr: %dバイト\n", unsafe.Sizeof(uintptr(0)))
	fmt.Printf("int: %dバイト\n", unsafe.Sizeof(0))
	fmt.Printf("int8: %dバイト\n", unsafe.Sizeof(int8(0)))
	fmt.Printf("int16: %dバイト\n", unsafe.Sizeof(int16(0)))
	fmt.Printf("int32: %dバイト\n", unsafe.Sizeof(int32(0)))
	fmt.Printf("int64: %dバイト\n", unsafe.Sizeof(int64(0)))
	fmt.Printf("float32: %dバイト\n", unsafe.Sizeof(float32(0)))
	fmt.Printf("float64: %dバイト\n", unsafe.Sizeof(float64(0)))
	fmt.Printf("complex64: %dバイト\n", unsafe.Sizeof(complex64(0)))
	fmt.Printf("complex128: %dバイト\n", unsafe.Sizeof(complex128(0)))
	fmt.Printf("string: %dバイト\n", unsafe.Sizeof(""))
	fmt.Printf("rune: %dバイト\n", unsafe.Sizeof('a'))
	fmt.Printf("bool: %dバイト\n", unsafe.Sizeof(false))
}
```

実行結果は次のとおりです。

```zsh
go-sample $ go run main.go
byte: 1バイト
uint: 8バイト
uint8: 1バイト
uint16: 2バイト
uint32: 4バイト
uint64: 8バイト
uintptr: 8バイト
int: 8バイト
int8: 1バイト
int16: 2バイト
int32: 4バイト
int64: 8バイト
float32: 4バイト
float64: 8バイト
complex64: 8バイト
complex128: 16バイト
string: 16バイト
rune: 4バイト
bool: 1バイト
```

### array

`array`は要素数と要素の型サイズによって確保するメモリ領域が変わります。

```go:main.go
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	fmt.Printf("array1: %dバイト\n", unsafe.Sizeof([1]int{1}))
	fmt.Printf("array2: %dバイト\n", unsafe.Sizeof([3]int8{1, 2, 3}))
	fmt.Printf("array3: %dバイト\n", unsafe.Sizeof([5]string{"a", "b", "c", "d", "e"}))
}
```

実行結果は下記のとおりです。

```zsh
go-sample $ go run main.go
array1: 8バイト
array2: 3バイト
array3: 80バイト
```

このように、要素の型サイズに要素数をかけ合わせた分が、arrayの型サイズとなるのです。

### slice, map, chan

`slice`, `map`, `chan`は要素の型サイズや要素数にかかわらず、固定の型サイズになります。

```go:main.go
	fmt.Printf("slice1: %dバイト\n", unsafe.Sizeof([]int{1}))
	fmt.Printf("slice2: %dバイト\n", unsafe.Sizeof([]int8{1, 2, 3}))
	fmt.Printf("slice3: %dバイト\n", unsafe.Sizeof([]string{"a", "b", "c", "d", "e"}))
	fmt.Printf("map1: %dバイト\n", unsafe.Sizeof(map[string]int{"a": 1}))
	fmt.Printf("map2: %dバイト\n", unsafe.Sizeof(map[string]int8{"a": 1, "b": 2, "c": 3}))
	fmt.Printf("map3: %dバイト\n", unsafe.Sizeof(map[int]bool{1: true, 2: false, 3: true, 4: false, 5: true}))
	fmt.Printf("chan1: %dバイト\n", unsafe.Sizeof(make(chan int, 1)))
	fmt.Printf("chan2: %dバイト\n", unsafe.Sizeof(make(chan int8, 3)))
	fmt.Printf("chan3: %dバイト\n", unsafe.Sizeof(make(chan string, 5)))
```

実行結果は次のとおりです。

```zsh
go-sample $ go run main.go
slice1: 24バイト
slice2: 24バイト
slice3: 24バイト
map1: 8バイト
map2: 8バイト
map3: 8バイト
chan1: 8バイト
chan2: 8バイト
chan3: 8バイト
```

`slice`は24バイト、`map`は8バイト、`chan`は8バイトで固定されていることがわかります。

### 構造体
構造体の型サイズは各フィールドの型サイズの合計となります。

サンプルコードで確認します。

```go:main.go
package main

import (
	"fmt"
	"unsafe"
)

type A struct {
	i int     // 8バイト
	s string  // 16バイト
	f float64 // 8バイト
}

type B struct {
	i int // 8バイト
	a A   // 8 + 16 + 8 = 32バイト
}

func main() {
	a := A{}
	fmt.Printf("A: %dバイト\n", unsafe.Sizeof(a))
	b := B{}
	fmt.Printf("B: %dバイト\n", unsafe.Sizeof(b))
}
```

実行結果は次のとおりです。

```zsh
go-sample $ go run main.go
A: 32バイト
B: 40バイト
```

構造体A, Bともに各フィールドの型サイズの合計であることがわかります。

※あ

### ポインタ

64bitマシンの場合、ポインタの型サイズは常に8バイトになります。

```go:main.go
package main

import (
	"fmt"
	"unsafe"
)

type A struct {
	i int
	s string
	f float64
}

type B struct {
	i int
	a A
}

func main() {
	a := A{}
	fmt.Printf("A: %dバイト\n", unsafe.Sizeof(&a))
	b := B{}
	fmt.Printf("B: %dバイト\n", unsafe.Sizeof(&b))
}
```

実行結果は次のとおりです。

```zsh
go-sample $ go run main.go
A: 8バイト
B: 8バイト
```

### interface
`interface`は常に固定で16バイトのメモリ領域を確保します。

```go:main.go
package main

import (
	"fmt"
	"unsafe"
)

type MultiMethod interface {
	MethodA() string
	MethodB() int
	MethodC(float64) bool
}

type X struct{}

func (x X) MethodA() string {
	return "MethodA"
}

func (x X) MethodB() int {
	return 42
}

func (x X) MethodC(f float64) bool {
	return f > 0
}

func main() {
	var m MultiMethod = X{}
	fmt.Printf("MultiMethodを格納したインターフェース: %dバイト\n", unsafe.Sizeof(m))
}
```

実行結果は次のとおりです。

```zsh
go-sample $ go run main.go
MultiMethodを格納したインターフェース: 16バイト
```

## おわりに

この記事では、Goの主要なデータ型がどのようにメモリ領域を確保するかを実例を交えて紹介しました。

メモリ消費を意識する場面では`unsafe.Sizeof`を活用して、実際のサイズを確認してみてください。

記事の内容に誤りがあれば、コメントにて教えていただけますと幸いです。

### 参考資料
- https://pkg.go.dev/github.com/shogo82148/std/unsafe