以下のようなものを観測しました。
if you can tell what's wrong with this struct, you are better than 99% of CS grads pic.twitter.com/WAjqr7aMtI
— ronin (@seatedro) 2025年1月2日
Go で動きを見てみました。
サンプルコード
package main import ( "fmt" "unsafe" ) type Inefficient struct { A uint32 B uint64 C uint32 } type Efficient struct { A uint32 C uint32 B uint64 } func checkInefficientStructMemoryAddressNumber(inefficient *Inefficient) { // 非効率な構造体 のアドレス baseAddrInefficient := uintptr(unsafe.Pointer(inefficient)) aAddrInefficient := unsafe.Offsetof(inefficient.A) bAddrInefficient := unsafe.Offsetof(inefficient.B) cAddrInefficient := unsafe.Offsetof(inefficient.C) // 非効率な構造体のメモリレイアウト表示 fmt.Printf("\n=== 非効率な構造体(合計: %d バイト)===\n", unsafe.Sizeof(Inefficient{})) fmt.Printf("構造体のベースアドレス: %x\n", baseAddrInefficient) fmt.Printf("A (uint32) のオフセット: %d バイト(アドレス: %x)\n", aAddrInefficient, baseAddrInefficient+aAddrInefficient) fmt.Printf("B (uint64) のオフセット: %d バイト(アドレス: %x)\n", bAddrInefficient, baseAddrInefficient+bAddrInefficient) fmt.Printf("C (uint32) のオフセット: %d バイト(アドレス: %x)\n", cAddrInefficient, baseAddrInefficient+cAddrInefficient) // メモリレイアウトの視覚化 fmt.Println("\nメモリレイアウト:") visualizeMemoryLayout(baseAddrInefficient, []struct { name string offset uintptr size int }{ {"A", aAddrInefficient, 4}, {"padding", 4, 4}, {"B", bAddrInefficient, 8}, {"C", cAddrInefficient, 4}, {"padding", 20, 4}, }) } func checkEfficientStructMemoryAddressNumber(efficient *Efficient) { // EfficientStruct のメンバーアドレス baseAddrEfficient := uintptr(unsafe.Pointer(efficient)) bAddrEfficient := unsafe.Offsetof(efficient.B) aAddrEfficient := unsafe.Offsetof(efficient.A) cAddrEfficient := unsafe.Offsetof(efficient.C) // 効率的な構造体のメモリレイアウト表示 fmt.Printf("\n=== 効率的な構造体(合計: %d バイト)===\n", unsafe.Sizeof(Efficient{})) fmt.Printf("構造体のベースアドレス: %x\n", baseAddrEfficient) fmt.Printf("B (uint64) のオフセット: %d バイト(アドレス: %x)\n", bAddrEfficient, baseAddrEfficient+bAddrEfficient) fmt.Printf("A (uint32) のオフセット: %d バイト(アドレス: %x)\n", aAddrEfficient, baseAddrEfficient+aAddrEfficient) fmt.Printf("C (uint32) のオフセット: %d バイト(アドレス: %x)\n", cAddrEfficient, baseAddrEfficient+cAddrEfficient) // メモリレイアウトの視覚化 fmt.Println("\nメモリレイアウト:") visualizeMemoryLayout(baseAddrEfficient, []struct { name string offset uintptr size int }{ {"B", bAddrEfficient, 8}, {"A", aAddrEfficient, 4}, {"C", cAddrEfficient, 4}, }) } func visualizeMemoryLayout(baseAddr uintptr, layout []struct { name string offset uintptr size int }) { for _, item := range layout { addr := baseAddr + item.offset fmt.Printf("%x: ", addr) if item.name == "padding" { fmt.Printf("[%s] (%d bytes)\n", item.name, item.size) } else { fmt.Printf("%s (%d bytes)\n", item.name, item.size) } } } func main() { fmt.Printf("Inefficient構造体の1つのサイズ: %d bytes\n", unsafe.Sizeof(Inefficient{})) fmt.Printf("Efficient構造体の1つのサイズ: %d bytes\n\n", unsafe.Sizeof(Efficient{})) inefficient := &Inefficient{A: 1, B: 2, C: 3} efficient := &Efficient{B: 2, A: 1, C: 3} checkInefficientStructMemoryAddressNumber(inefficient) checkEfficientStructMemoryAddressNumber(efficient) }
実行
% go run main.go
出力
Inefficient構造体の1つのサイズ: 24 bytes Efficient構造体の1つのサイズ: 16 bytes === 非効率な構造体(合計: 24 バイト)=== 構造体のベースアドレス: c00008ef08 A (uint32) のオフセット: 0 バイト(アドレス: c00008ef08) B (uint64) のオフセット: 8 バイト(アドレス: c00008ef10) C (uint32) のオフセット: 16 バイト(アドレス: c00008ef18) メモリレイアウト: c00008ef08: A (4 bytes) c00008ef0c: [padding] (4 bytes) c00008ef10: B (8 bytes) c00008ef18: C (4 bytes) c00008ef1c: [padding] (4 bytes) === 効率的な構造体(合計: 16 バイト)=== 構造体のベースアドレス: c00008eef8 B (uint64) のオフセット: 0 バイト(アドレス: c00008eef8) A (uint32) のオフセット: 8 バイト(アドレス: c00008ef00) C (uint32) のオフセット: 12 バイト(アドレス: c00008ef04) メモリレイアウト: c00008eef8: B (8 bytes) c00008ef00: A (4 bytes) c00008ef04: C (4 bytes)
確認
メモリレイアウト: c00008ef08: A (4 bytes) c00008ef0c: [padding] (4 bytes) c00008ef10: B (8 bytes) c00008ef18: C (4 bytes) c00008ef1c: [padding] (4 bytes)
メモリは1バイトずつ連続的にアドレスが割り当てられています。16進数で1ずつ増えていっています。
c00008ef08: A の1バイト目 c00008ef09: A の2バイト目 c00008ef0a: A の3バイト目 c00008ef0b: A の4バイト目 c00008ef0c: パディング1バイト目 c00008ef0d: パディング2バイト目 c00008ef0e: パディング3バイト目 c00008ef0f: パディング4バイト目 c00008ef10: B の1バイト目 c00008ef11: B の2バイト目 c00008ef12: B の3バイト目 c00008ef13: B の4バイト目 c00008ef14: B の5バイト目 c00008ef15: B の6バイト目 c00008ef16: B の7バイト目 c00008ef17: B の8バイト目 c00008ef18: C の1バイト目 c00008ef19: C の2バイト目 c00008ef1a: C の3バイト目 c00008ef1b: C の4バイト目 c00008ef1c: パディング1バイト目 c00008ef1d: パディング2バイト目 c00008ef1e: パディング3バイト目 c00008ef1f: パディング4バイト目
メモリレイアウト: c00008eef8: B (8 bytes) c00008ef00: A (4 bytes) c00008ef04: C (4 bytes)
c00008eef8: B の1バイト目 c00008eef9: B の2バイト目 c00008eefa: B の3バイト目 c00008eefb: B の4バイト目 c00008eefc: B の5バイト目 c00008eefd: B の6バイト目 c00008eefe: B の7バイト目 c00008eeff: B の8バイト目 c00008ef00: A の1バイト目 c00008ef01: A の2バイト目 c00008ef02: A の3バイト目 c00008ef03: A の4バイト目 c00008ef04: C の1バイト目 c00008ef05: C の2バイト目 c00008ef06: C の3バイト目 c00008ef07: C の4バイト目
参考
*データ構造アライメント - Wikipedia * unsafe package - unsafe - Go Packages