メモリレイアウト・パディング・アラインメント・エンディアン¶
プロセスのメモリ地図¶
実行中のプログラム(プロセス)の仮想アドレス空間は、役割ごとの領域に分かれています(配置は典型例。OSにより詳細は異なる)。
高アドレス
┌───────────────┐
│ スタック(下に成長)│ ← ローカル変数、関数呼び出しのフレーム(自動記憶域)
│ ↓ │
│ (空き) │
│ ↑ │
│ ヒープ(上に成長) │ ← new/malloc(動的記憶域)
├───────────────┤
│ .bss │ ← ゼロ初期化のグローバル/静的変数
│ .data │ ← 初期化済みグローバル/静的変数(静的記憶域)
│ .rodata │ ← 定数、文字列リテラル、vtable(読み取り専用)
│ .text │ ← 機械語(コード領域。読み取り+実行のみ)
└───────────────┘
低アドレス
- スタック: 確保はポインタをずらすだけ(超高速)。サイズ上限あり(典型1〜8MB → 深い再帰や巨大ローカル配列でstack overflow)。寿命はスコープ
- ヒープ: 任意サイズ・任意寿命。確保は管理コストあり、断片化する
- 書けない領域に書くとOSがアクセス違反(Segmentation Fault / Access Violation)で止める——「ぬるぽで即死」の正体は大抵、低アドレス(マップされていないページ)への書き込み
各領域の「寿命」の言語上の分類(記憶域期間)は第7部で扱います。
アラインメントとパディング¶
CPUは「型のサイズの倍数のアドレス」にあるデータを効率的に(または正しく)読みます。intは4の倍数、doubleは8の倍数……これがアラインメント(整列)要求です。コンパイラは要求を満たすため、構造体にパディング(詰め物)を入れます。
// C++20
struct Bad { // メンバの並びが悪い例
char a; // 1バイト (offset 0)
// パディング7 (dが8の倍数に置かれるように)
double d; // 8バイト (offset 8)
char b; // 1バイト (offset 16)
// 末尾パディング7 (配列にしたとき次の要素のdが揃うように)
}; // sizeof(Bad) == 24
struct Good { // 大きい順に並べただけ
double d; // offset 0
char a; // offset 8
char b; // offset 9
// 末尾パディング6
}; // sizeof(Good) == 16(33%削減)
検証済みサンプル: samples/struct_layout.cpp(offsetof/sizeofでの観察)
- パディングの入り方は実装定義だが、上記は主要環境(x64のMSVC/GCC/Clang)の典型
- 大量に持つ構造体(パーティクル、ECSコンポーネント)ではサイズ=キャッシュ効率=速度。メンバは大きい順が基本テク(→ Data Locality)
alignas(16)でSIMD用の16バイト整列を強制、static_assert(sizeof(Particle) == 32)でサイズをビルド時固定、が実務の道具- バイナリ保存の罠: 構造体をそのままfwriteすると、パディング(ゴミ)込みで書かれ、コンパイラ/設定が変わると読めなくなる。シリアライズはメンバ単位で
エンディアン¶
複数バイトの数値をメモリに置くバイト順です。
int x = 0x12345678; をアドレスaに置くと:
リトルエンディアン(x86/x64, ARMの通常設定): a:[78][56][34][12] ← 下位バイトが先
ビッグエンディアン(ネットワークバイト順など): a:[12][34][56][78]
- 現代のゲーム機・PC・スマホは実質すべてリトルエンディアン。問題になるのは境界: ネットワークプロトコル(ビッグ=ネットワークバイト順の伝統)、他機種とのセーブデータ互換、バイナリフォーマットの仕様書
- エンディアンは実装定義。C++20の
std::endianでコンパイル時判定できる
C#との対応¶
- C#にも構造体レイアウトとパディングは存在する(
[StructLayout],Marshal.SizeOf)が、通常は意識しない(ランタイムが管理し、フィールド順の再配置すらする)。C++は書いた順に並ぶ(標準レイアウト型なら保証)ことが逆に道具になる - Unityで意識する瞬間: ComputeShaderへの構造体転送、NativeArray、P/Invokeの構造体マーシャリング——このページの知識がそのまま必要になる場面
Unity開発者が誤解しやすい点¶
- 「メモリはフラットな置き場」— 領域ごとに性質(速度・寿命・保護)が違う。スタックとヒープの確保コスト差は桁違い
- 「structのサイズ=メンバの合計」— パディングで増える。順序で変わる
- 「セーブデータはmemcpyで一発」— パディング・エンディアン・型サイズ(実装定義)の三重の罠
ゲーム開発での使用例¶
- パーティクル/コンポーネントのサイズ削減(並び替え、ビットフィールド、量子化)
- SIMD用の
alignas(16)整列(→ SIMD) - ネットワークパケットの明示的レイアウト(固定幅型
uint32_t+エンディアン変換) - クラッシュダンプでアドレスを見て「これはスタック?ヒープ?コード?」を推定する力
理解度チェック¶
- スタックとヒープの確保コスト・寿命・サイズ上限の違いを言えますか。
- パディングはなぜ入りますか。減らす基本テクは?
- 構造体のfwrite保存が壊れやすい理由を2つ挙げてください。
演習¶
samples/struct_layout.cpp で、(a) メンバ並び替えによるsizeof変化、(b) offsetofでパディング位置の特定、(c) 可能ならalignasの効果を確認してください。
前: RTTI・例外処理の仕組み | カテゴリ目次 | 次: 関数呼び出し・コールスタック・呼出規約