いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-143 (構造体のメンバ定義順によるメモリのパディングについて)

概要

小ネタ。基本的にどの言語でも同じ理屈です。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

参考資料

itnext.io

ja.wikipedia.org


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com