概要
小ネタ。基本的にどの言語でも同じ理屈です。C言語やってる人とか通信プログラム書いてる人にはおなじみの話だと思います。
構造体を定義する際、メンバの定義順によっては想定しているよりも大きなサイズでメモリ上に確保されることがあります。
例えば、以下の構造体があったとします。
type S struct { Flag bool Value int16 }
この構造体は、メンバとして bool を一つ、int16 を一つもっています。boolは1バイト、int16は2バイトなので、この構造体のサイズは3バイトと思っちゃうところですが
s := S{} fmt.Println(unsafe.Sizeof(s))
とすると、結果は 4
って出ます。なんか多い。。。。
何故かっていうと、メモリのパディングが発生していて、bool のメンバ Flag の後ろに 1バイト 分追加されているからです。
これはメモリのアライメントが発生していて、コンピュータの都合が良いサイズになるように調整されているからです。
そのコンピュータのワードサイズになるように調整されるので、大抵4か8の倍数になります。
なので、メンバの定義順によってパディングが発生するようになったり、ならなく出来たりも出来ます。
まあ、とてもメモリサイズやデータの並びにシビアな場合以外は、そんなに気にする必要はないですが、知ってるとちょっと便利な時もあります。
サンプル
以下のような構造体を定義しているとします。
package types // Struct4Bytes は、メンバーのサイズで見ると 3bytes なのに、メモリ上のサイズが 4bytes になる構造体です. type Struct4Bytes struct { Flag bool Value int16 } // Struct8Bytes は、メンバーのサイズで見ると 5bytes なのに、メモリ上のサイズが 8bytes になる構造体です. type Struct8Bytes struct { Flag bool Value int32 } // MemoryPadding は、メンバー定義順によってメモリのパディングが発生する構造体です. type MemoryPadding struct { Flag1 bool ShortVal int16 Flag2 bool FloatVal float32 } // NoMemoryPadding は、メンバー定義順を考慮してメモリのパティングが発生しないようにしている構造体です. type NoMemoryPadding struct { FloatVal float32 ShortVal int16 Flag1 bool Flag2 bool } func (MemoryPadding) Layout() string { return ` | Flag1 | | ShortVal | Flag2 | | FloatVal | ----------------------------------------------------------------------------- | bool (1) | padding (1) | int16 (2) | bool (1) | padding (3) | float32 (4) | | 4 | 4 | 4 | ` } func (NoMemoryPadding) Layout() string { return ` | FloatVal | ShortVal | Flag1 | Flag2 | ------------------------------------------------- | float32 (4) | int16 (2) | bool (1) | bool (1) | | 4 | 4 | ` }
サンプルが以下。
package structs import ( "unsafe" "github.com/devlights/gomy/output" "github.com/devlights/try-golang/internal/examples/basic/structs/types" ) // MemoryPadding は、構造体メンバーの定義順によってGoランタイムがメモリ上にパディングを挿入することを確認するサンプルです. // // REFERENCES: // - https://itnext.io/structure-size-optimization-in-golang-alignment-padding-more-effective-memory-layout-linters-fffdcba27c61 // - https://logicalbeat.jp/blog/4032/ // - https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0%E3%82%A2%E3%83%A9%E3%82%A4%E3%83%A1%E3%83%B3%E3%83%88 func MemoryPadding() error { var ( st4bytes = types.Struct4Bytes{} // メモリ上のサイズが 4bytes になる構造体 st8bytes = types.Struct8Bytes{} // メモリ上のサイズが 8bytes になる構造体 notGood = types.MemoryPadding{} // メモリのパディングが発生する構造体 good = types.NoMemoryPadding{} // メモリのパディングが発生しない構造体 ) output.Stdoutf("[st4bytes]", "%d byte(s)\n", unsafe.Sizeof(st4bytes)) output.StdoutHr() output.Stdoutf("[st8bytes]", "%d byte(s)\n", unsafe.Sizeof(st8bytes)) output.StdoutHr() output.Stdoutf("[Padding 発生]", "%d byte(s)\n", unsafe.Sizeof(notGood)) output.Stdoutl("", notGood.Layout()) output.StdoutHr() output.Stdoutf("[Padding なし]", "%d byte(s)\n", unsafe.Sizeof(good)) output.Stdoutl("", good.Layout()) return nil }
実行すると以下のようになります。
gitpod /workspace/try-golang $ make run go get -d ./... go run -race github.com/devlights/try-golang/cmd/trygolang -onetime -example "" ENTER EXAMPLE NAME: struct_memory_padding [Name] "struct_memory_padding" [st4bytes] 4 byte(s) -------------------------------------------------- [st8bytes] 8 byte(s) -------------------------------------------------- [Padding 発生] 12 byte(s) | Flag1 | | ShortVal | Flag2 | | FloatVal | ----------------------------------------------------------------------------- | bool (1) | padding (1) | int16 (2) | bool (1) | padding (3) | float32 (4) | | 4 | 4 | 4 | -------------------------------------------------- [Padding なし] 8 byte(s) | FloatVal | ShortVal | Flag1 | Flag2 | ------------------------------------------------- | float32 (4) | int16 (2) | bool (1) | bool (1) | | 4 | 4 | [Elapsed] 271.353µs
参考資料
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場