0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

スタック・ヒープの関係整理

Last updated at Posted at 2025-10-05

はじめに

プログラムが動くとき、CPU はメモリの中の「どの場所に何を置くか」を決めています。
その中でも特に重要なのが スタック領域ヒープ領域
関数を使ったプログラム(C やアセンブラ)では、
「一時的に使うもの」はスタック、「長く残すもの」はヒープに置く、という住み分けがあります。

  • スタック … CPUが関数の呼び出しを正しく行うための一時的な作業机
  • ヒープ … プログラムが自由に使える長期保管スペース

スタック(stack)

関数を呼び出すとき、CPUは「戻り先アドレス」「関数の引数」「ローカル変数」などを一時的に記録します。
それらをまとめて置く場所がスタックです。

スタックが必要な理由(追記)

関数を呼び出すたびに「どこから来たのか」「何を持っていたのか」を記録しないと、
終わったあとに元の場所へ戻れません。
この「一時的な記録帳」こそがスタックです。

C では自動でやってくれますが、アセンブラでは自分でやる必要があります。
だから、スタックを理解することは「Cが裏でやっていることを理解する」ことでもあります。

アセンブラとのつながりを補足

Cで func(); と書くだけで関数を呼べるのは、
コンパイラが自動的に「BSR(呼び出し)」「RTS(復帰)」「PUSH/POP(退避)」を生成してくれているから。

つまり、

  • C言語 … スタック操作を「隠蔽」してくれる高級言語
  • アセンブラ … すべて自分の手でやる低級言語

この違いが、Cとアセンブラの根本的な差です。

🔹 サブルーチン呼び出しの全体図

呼び出し側(main):
   BSR sub_func      ; サブルーチンへ(戻り先を SP が指す位置に PUSH)
   ↓
[スタック] ← 戻り先アドレスを自動で積む
   ↓
sub_func:
   PUSH R1-R3        ; 使用するレジスタを一時退避
   ...
   POP  R1-R3        ; 戻す
   RTS               ; スタックから戻り先を取り出して帰る

つまりアセンブラの関数呼び出しは「3段構成」

  1. 戻り先をスタックに保存(BSR)
  2. サブルーチンで必要なレジスタを退避(PUSH)
  3. 終了時に復帰(RTS + POP)

これが「Cが裏でやってくれること」の正体です。

C言語とアセンブラの関係図

概念 C言語での記述 アセンブラでの動作 説明
関数呼び出し func(); BSR func 戻り先をスタックに積む
関数終了 return; RTS スタックから戻り先を復帰
ローカル変数 int x; SUB #size, SP など スタックに一時領域を確保
関数引数 func(a, b) スタック経由またはレジスタ渡し 呼び出し時にコピーされる
保存レジスタ PUSH/POP 関数内で壊さないために退避

ヒープ(heap)

Cでは malloc などで自分で確保・解放する領域です。
スタックとは異なり、関数を抜けてもメモリが残るのが特徴です。
ヒープに確保されたデータは関数の外でも有効で、複数の関数から共有することもできます。

なぜヒープが必要か

スタックは「関数が終われば自動的に消える一時的な領域」なので、
関数の外でも使いたいデータを置くことができません。

たとえば、配列を関数内で作って返したいとき:

int* makeArray(int n) {
    int ary[10];   // スタックに確保 → 関数を抜けると消える
    return ary;    // ❌ 無効なアドレスを返すことになる
}

これを避けるために、ヒープに確保します。

int* makeArray(int n) {
    int *ary = malloc(n * sizeof(int));  // ヒープに確保
    return ary;                          // ✅ 関数を抜けても残る
}

オブジェクト指向言語とヒープ

C やアセンブラでは、
「どのメモリに置くか」を自分で指定しなければなりません。
しかし、**オブジェクト指向言語(Java, C#, Python, PHP など)**では
メモリ管理を「オブジェクト単位」で自動化しています。
オブジェクト指向言語では、変数に実体そのものではなく「参照(アドレスの安全版)」が入る。

すべてのオブジェクトはヒープに置かれる

たとえば:

int[] ary = new int[5];
Person p = new Person();

このとき、

  • aryp 自体はヒープ上に作られ
  • 変数 aryp は「ヒープ上の実体を指す参照(reference)」を持っています。

つまり:

C のポインタとほぼ同じ仕組みを、安全に・自動で扱っているのがオブジェクト指向言語。

GC(ガーベジコレクション)が登場する理由

Cでは、メモリの確保と解放をすべて自分で書く必要がありました。
→ その煩雑さをなくすために登場したのがGCです。

Cのように free() を忘れるとメモリが残り続ける(リーク)問題が起こります。
オブジェクト指向言語ではこの煩雑さをなくすために、
使われなくなったオブジェクトを自動で検出・解放する仕組み(GC)が導入されました。

言語 ヒープ確保 解放の仕組み
C malloc() free() 手動
C++ new delete 手動(スマートポインタで補助あり)
Java / C# new 自動(GC)
Python / PHP オブジェクト生成 自動(参照カウント + GC)

まとめ:C と オブジェクト指向言語の違い

観点 C言語 オブジェクト指向言語
メモリ確保 malloc / free 手動 new / 自動
メモリ位置 スタック or ヒープ 基本的にヒープ
データの扱い 値そのものを操作 参照(ポインタ安全版)を操作
管理方式 プログラマが管理 ガーベジコレクタが管理
メモリ寿命 関数内で終わる 参照がなくなるまで残る

補足:アセンブラとの関係

  • アセンブラ:メモリの確保も解放もすべて自分で命令を書く(物理アドレス単位)
  • C:malloc/free で論理的に制御
  • オブジェクト指向言語:new で論理的に生成、GCが自動解放

つまり、

アセンブラ → C → オブジェクト指向
と進むにつれて、「メモリ操作をどんどん自動化」していった流れになります。

全体のつながり(図でイメージ)

下図は、同じ「関数・メモリ管理」という仕組みが、言語の層ごとにどのように実装されているかを示したものです。

        ┌───────────────┐
        │ 高級言語層     │  ← new, malloc, 関数呼び出し(自動)
        ├───────────────┤
        │ Cコンパイラ出力 │  ← スタック操作命令(PUSH/POP, CALL/RET)
        ├───────────────┤
        │ アセンブラ層   │  ← 実際のメモリ操作(ISP, SP, MOV, BSR, RTS)
        ├───────────────┤
        │ CPU・RAM       │  ← スタック領域, ヒープ領域が実体化
        └───────────────┘
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?