本文へスキップ
BecomeCoder

C言語コース · 第6章 ポインタとメモリ · レッスン26

スタックとヒープ ― メモリの使い分け(読み物)

ローカル実施

導入

ここまでで「変数メモリに置かれ、住所を持つ」ことを学びました。最後に、そのメモリが実は2つの領域に大きく分かれて使われていることを紹介します。スタックヒープです。ポインタが本当に力を発揮するのは、この「ヒープ」を扱うときです。

説明

プログラムが動くとき、メモリはおおよそ次のように使い分けられます。

flowchart TB
  subgraph RAM["プログラムが使うメモリの全体像"]
    direction TB
    code["コード領域:プログラムの命令そのもの"]
    global["静的領域:グローバル変数など(ずっと居座る)"]
    heap["ヒープ:自分で確保・解放する広い領域"]
    stack["スタック:関数呼び出しと局所変数の一時置き場(自動)"]
  end
  stack -->|"呼び出しで伸び、戻ると縮む"| stack
  heap -->|"必要なときに好きなだけ借りる"| heap

スタック (stack) は、関数呼び出しのたびに自動で使われる領域です。関数に入ると、その関数のローカル変数(int x など)がスタックに積まれ、関数を抜けると自動でまとめて片付けられます。速くて手間いらずですが、「関数を抜けると消える」「大きさは前もって決まっている」という制約があります。これまでの章で使ってきたローカル変数は、すべてこのスタック上にあります。

flowchart TB
  m["main() の枠<br/>ローカル変数"] --> f["func() を呼ぶと<br/>上に積まれる"] --> g["さらに呼ぶと<br/>また積まれる"]
  g -.->|"関数を抜けると上から順に片付く"| m

ヒープ (heap) は、プログラマが「今これだけ必要」と明示して借りる、大きくて自由な領域です。スタックとは違い、関数を抜けても自動では片付きません。借りたメモリの場所は住所(ポインタ)で受け取ります。だから、ヒープを扱うにはポインタが欠かせません。

スタックとヒープの違いを整理します。

スタックヒープ
確保・解放自動(関数の出入りで)手動(自分で確保・解放)
寿命関数を抜けると消える自分で解放するまで残る
速さとても速いやや遅い
大きさ小さめ・固定的大きく取れる・可変
使いどころ一時的なローカル変数実行中に大きさが決まるデータ

なぜヒープが必要なのでしょうか。「実行してみないと必要な個数が分からない」データ(利用者が入力した件数ぶんの配列など)は、スタックの固定サイズでは足りません。そこで、実行中に必要なだけヒープから借りるという考え方が出てきます。ただし借りたら必ず返すのがルールで、返し忘れると使わないメモリが増え続ける メモリリーク になります。

flowchart LR
  A["ヒープから借りる"] --> B["ポインタで使う<br/>*p = ..."] --> C["使い終わったら返す"]
  B -.->|"返し忘れると"| D["メモリリーク<br/>(無駄が増え続ける)"]

C では、この「ヒープから借りる」「使い終わったら返す」という2つの動作を、それぞれ専用の関数を呼んで行います。借りるときも返すときも、やり取りするのは**住所(ポインタ)**です――このレッスンで学んだ「ポインタで住所を扱う」という土台があってこそ、ヒープが使えるようになります。具体的な使い方は、次の章から実際に手を動かして学んでいきます。

まとめ

  • メモリは大きくスタック(自動・一時的)とヒープ(手動・自由)に分かれる。
  • ローカル変数はスタック、実行中に必要なだけ借りるのはヒープ。
  • ヒープの場所はポインタで受け取って使う――これがポインタの最も重要な役割のひとつ。
  • 借りたら返す。返し忘れはメモリリーク。次の章から、実際に借りる/返す方法を学んでいきます。