TL

スタックとヒープの実態(コールスタックとフレーム)

なぜローカル変数は速くて再帰は落ちるのか。コールスタックのフレーム構成と戻りアドレス、ヒープの動的確保を配置図で押さえ、性能とクラッシュの勘所をつかむ。

応用メモリコールスタックヒープ低レベルアロケータ最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.コールスタックは関数呼び出しごとにフレームを高位アドレスから低位へ積み、戻りアドレス・退避レジスタ・ローカル変数を一括で確保/解放する。
  • 2.スタックは確保・解放がスタックポインタの加減算のみで定数時間、ヒープはアロケータが空き領域を管理するため割り当てコストと断片化が生じる。
  • 3.深い再帰や巨大なローカル配列はスタック上限を超えてスタックオーバーフローを起こし、隣接領域を踏むとガードページ違反でクラッシュする。

プロセスのメモリ配置

実行中のプロセスは仮想アドレス空間を持ち、その中で領域が役割ごとに分かれています。下端(低位アドレス)にコードとグローバルデータが置かれ、上端(高位アドレス)寄りにスタックが、その中間にヒープが配置されるのが典型です。両者は反対向きに伸び、間の未使用領域を奪い合います。

高位アドレス
┌──────────────┐
│   スタック    │  ← 関数呼び出しで下へ伸びる(フレームを積む)
├──────────────┤
│      ↓        │
│  (未使用)    │
│      ↑        │
├──────────────┤
│    ヒープ     │  ← malloc/new で上へ伸びる
├──────────────┤
│  BSS / Data   │  グローバル・静的変数
├──────────────┤
│   テキスト     │  機械語コード(読み取り専用)
└──────────────┘
低位アドレス

スタックとヒープは「動的に増減する領域」という点で似ていますが、管理者が誰かが決定的に違います。スタックはコンパイラが生成した命令とハードウェアのスタックポインタが自動管理し、ヒープはアロケータ(malloc 実装や言語ランタイム)が明示的な要求に応じて管理します。

コールスタックとフレーム構成

関数を呼ぶたびに、その呼び出し1回分の作業領域を1枚のスタックフレーム(活性化レコード)として確保します。フレームには概ね次が含まれます。

  • 戻りアドレス — 呼び出し元で次に実行すべき命令の位置。call 命令がスタックへ自動的に積みます。
  • 退避レジスタ — 呼び出し規約で「呼び出し先が保存する」とされたレジスタの元の値。
  • ローカル変数とテンポラリ — その関数内だけで使う変数や中間結果。
  • 引数の一部 — レジスタに乗り切らない引数や、可変長引数など。

確保はスタックポインタ(SP)を必要バイトだけ減算するだけ、解放は加算で元に戻すだけです。つまりフレーム内の何十個の変数も、SPの1回の加減算でまとめて確保・破棄されます。これがスタック確保が定数時間で極めて安い理由です。

呼び出しの流れ:  main → compute → helper

│  main のローカル      │  高位(main のフレーム)
├──────────────────────┤
│  戻りアドレス→main    │  ┐
│  compute のローカル    │  ┘ compute のフレーム
├──────────────────────┤
│  戻りアドレス→compute │  ┐
│  helper のローカル     │  ┘ helper のフレーム ← 現在の SP(最も低位)
└──────────────────────┘  低位

helper が return すると、SPが戻りアドレスの位置まで巻き戻り、そこへジャンプして compute の続きが走ります。フレームは**後入れ先出し(LIFO)**で積み下ろしされ、これが「スタック」と呼ばれる所以です。多くのABIではフレームの基準位置を指すフレームポインタ(x86-64 の RBP など)も併用し、デバッガがフレームを順にたどれるようにします。なお 再帰 はこの仕組みを直接利用しており、呼び出し1段ごとに独立したフレームが積まれます。

スタックオーバーフロー

スタックには上限があります(Linux の既定で 8 MB 程度、スレッドごとに固定)。フレームを積みすぎてこの上限を超えると、スタックオーバーフローが発生します。代表的な原因は二つです。

  • 深すぎる、あるいは止まらない再帰 — 終了条件のないループ的再帰は、フレームを無限に積んでいきます。
  • 巨大なローカル変数 — 関数内で char buf[10_000_000] のような大きな配列を取ると、1フレームで上限を食い潰します。
オーバーフローは隣接領域を踏む前に止められる

OSはスタックの末端に書き込み不可のガードページを1枚置きます。伸びすぎたスタックがそこへ触れると保護違反(セグメンテーション違反)が発生し、ヒープや他の領域を静かに破壊する前にプロセスを止められます。深い再帰を避けられないアルゴリズムは、明示的なスタック(ヒープ上の配列)へ展開するか、末尾呼び出し最適化 でフレームを再利用する形へ書き換えるのが定石です。

ヒープと動的確保

スタックでは寿命が「関数の入れ子」に縛られます。関数を抜けてもなお生き続けるデータや、実行時までサイズが決まらないデータにはヒープを使います。mallocnew・各言語の確保命令でアロケータに領域を要求し、不要になったら freedelete、あるいは ガベージコレクション で回収します。

アロケータは、空き領域をフリーリストなどの内部データ構造で管理します。要求が来るたびに十分な大きさの空きブロックを探し(first-fit、best-fit など)、切り出して返します。ここに二つのコストが生まれます。

  • 割り当てコスト — 適切な空きブロックの探索・分割・メタデータ更新が必要で、SPの加減算1回で済むスタックより重い。
  • 断片化 — 確保と解放を繰り返すと空きが細切れになり、合計は足りるのに連続した大ブロックが取れない状態に陥る。

確保したアドレスを取り違えると、解放漏れ(メモリリーク)、二重解放、解放済み領域へのアクセス(use-after-free)といった深刻なバグになります。これらの所在の管理には ポインタと参照 の理解が前提になります。

スタックとヒープの比較

観点スタックヒープ
管理者コンパイラ+SP(自動)アロケータ/GC(明示要求)
確保・解放コスト定数時間(SPの加減算)探索・分割が必要で可変
寿命フレームに連動(関数を抜けると消滅)解放/回収まで任意に存続
サイズ上限が小さく固定(例 8MB)アドレス空間まで大きく取れる
典型的な失敗スタックオーバーフローリーク・断片化・use-after-free
局所性高い(連続領域で温かい)ばらつく(断片化で冷えやすい)
どちらに置くかは寿命とサイズで決める

原則は「フレームの寿命で足り、サイズが小さく既知ならスタック、寿命が呼び出しを超える/サイズが実行時依存/巨大ならヒープ」です。スタックは速く局所性も良いので、迷ったらまずスタックを検討し、寿命やサイズの制約に当たったときだけヒープへ逃がすと、性能と安全性のバランスが取りやすくなります。

まとめ

コールスタックは関数呼び出し1回ぶんのフレームを後入れ先出しで積み、戻りアドレスとローカル変数を一括管理します。確保・解放がSPの加減算だけで済むため高速ですが、上限が小さく、深い再帰や巨大なローカル変数はスタックオーバーフローを招きます。一方ヒープは寿命とサイズの制約を外す代わりに、アロケータによる探索コストと断片化、そして解放管理の難しさを背負います。両者の境界を「誰が管理し、いつ消えるか」で捉えることが、性能とクラッシュの両面を見通す鍵です。

プログラミング Article

スタックとヒープの実態(コールスタックとフレーム)を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

メモリ

比較で見る軸

難易度: advanced / カテゴリ: プログラミング / タグ数: 5

導入後に効く点

スタックは確保・解放がスタックポインタの加減算のみで定数時間、ヒープはアロケータが空き領域を管理するため割り当てコストと断片化が生じる。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
プログラミング
タグ数
5

判断チェックリスト

  • 自社の用途が「メモリ / コールスタック」に近いか確認する。
  • 強みである「コールスタックは関数呼び出しごとにフレームを高位アドレスから低位へ積み、戻りアドレス・退避レジスタ・ローカル変数を一括で確保/解放する。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

メモリコールスタックヒープ低レベルアロケータメモリコールスタックヒープ