いろいろ備忘録日記

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

Goメモ-640 (unsafeパッケージを使って構造体のメモリレイアウトを見る)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

ここ最近、unsafeパッケージについてのメモをちょこちょこしているので、ついでなので unsafe パッケージの他の関数についてもメモ残しておこうと思いました。

今回は、構造体のメモリレイアウトを見るについて。

面白いのが、パディングが入るように構造体を定義して、それをダンプしてみるとパディング部分にゴミが見える点です。

Goの仕様では、構造体のフィールドは初期化されますが、アライメントのためのパディング領域は未定義です。(言語仕様に記載が無い)

なので、実行するたびに変な値が見えたりします。まあ、パディングのエリアは値何でも良いので挙動に何も影響は及びません。

サンプル

unsafe_dump.go

package unsafes

import (
    "encoding/hex"
    "fmt"
    "math"
    "os"
    "unsafe"
)

// Dump は、unsafeパッケージを使って構造体のメモリダンプを出力するサンプルです。
func Dump() error {
    type (
        // わざとパディングが入る構造体とする
        // size: 24bytes
        S1 struct {
            A uint8  // offset=0,  size=1, padding=3
            B uint32 // offset=4,  size=4, padding=0
            C uint64 // offset=8,  size=8, padding=0
            D uint16 // offset=16, size=2, padding=6
        }
    )

    // 構造体のメモリダンプ
    //   尚、実行すると以下のように
    //        00000000  ff 62 a6 00 ff ff ff ff  ff ff ff ff ff ff ff ff
    //        00000010  ff ff 1e 00 c0 00 00 00
    //   62 a6 のようなゴミが見える。これはGoはパディング部分をゼロ初期化しないため。
    //   Goの仕様では、構造体のフィールドは初期化されるが、アライメントのためのパディング領域は未定義となっている。
    var (
        s1        = S1{math.MaxUint8, math.MaxUint32, math.MaxUint64, math.MaxUint16}
        size      = unsafe.Sizeof(s1)
        ptr       = unsafe.Pointer(&s1)
        bytePtr   = (*byte)(ptr)
        byteSlice = (([]byte)(unsafe.Slice(bytePtr, size)))
    )
    hex.Dumper(os.Stdout).Write(byteSlice)
    fmt.Println("")

    // ゼロクリアしてから値を再設定して確認してみる
    // 以下のループはmemset(ptr, 0, size) と同じ感じ。
    for i := range int(size) {
        if i == 0 {
            *bytePtr = 0
        }

        ptr = unsafe.Add(ptr, 1)
        bytePtr = (*byte)(ptr)
        *bytePtr = 0
    }

    // 構造体の値を設定しなおし
    s1.A = math.MaxUint8
    s1.B = math.MaxUint32
    s1.C = math.MaxUint64
    s1.D = math.MaxUint16

    hex.Dumper(os.Stdout).Write(byteSlice)

    return nil
}

実行

       $ task
        task: [build] go build -o "/home/dev/dev/github/try-golang/try-golang" .
        task: [run] ./try-golang -onetime

        ENTER EXAMPLE NAME: unsafe_dump

        [Name] "unsafe_dump"
        00000000  ff 00 00 00 ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
        00000010  ff ff 18 00 c0 00 00 00
        00000000  ff 00 00 00 ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
        00000010  ff ff 00 00 00 00 00 00

        [Elapsed] 88.436µs

参考情報

個人的Goのおすすめ書籍

個人的に読んでとても勉強になった書籍さんたちです。


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

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