導入
ここまでで「変数はメモリに置かれ、住所を持つ」ことを学びました。最後に、そのメモリが実は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つの動作を、それぞれ専用の関数を呼んで行います。借りるときも返すときも、やり取りするのは**住所(ポインタ)**です――このレッスンで学んだ「ポインタで住所を扱う」という土台があってこそ、ヒープが使えるようになります。具体的な使い方は、次の章から実際に手を動かして学んでいきます。
まとめ
- メモリは大きくスタック(自動・一時的)とヒープ(手動・自由)に分かれる。
- ローカル変数はスタック、実行中に必要なだけ借りるのはヒープ。
- ヒープの場所はポインタで受け取って使う――これがポインタの最も重要な役割のひとつ。
- 借りたら返す。返し忘れはメモリリーク。次の章から、実際に借りる/返す方法を学んでいきます。