導入
ここまでで「変数はメモリに置かれ、住所を持つ」ことを学びました。最後に、そのメモリが実は2つの領域に大きく分かれて使われていることを紹介します。スタックとヒープです。ポインタが本当に力を発揮するのは、この「ヒープ」を扱うときです。
このレッスンは概念の理解が目的の読み物です。動的確保(
new/delete)はブラウザ内の学習用インタプリタでは扱わないため、手元の本物のコンパイラ(Visual Studio や g++/clang)で試すことを想定しています。
説明
プログラムが動くとき、メモリはおおよそ次のように使い分けられます。
flowchart TB
subgraph RAM["プログラムが使うメモリの全体像"]
direction TB
code["コード領域:プログラムの命令そのもの"]
global["静的領域:グローバル変数など(ずっと居座る)"]
heap["ヒープ:自分で確保・解放する広い領域(new / delete)"]
stack["スタック:関数呼び出しと局所変数の一時置き場(自動)"]
end
stack -->|"呼び出しで伸び、戻ると縮む"| stack
heap -->|"必要なときに好きなだけ借りる"| heap
スタック (stack) は、関数呼び出しのたびに自動で使われる領域です。関数に入ると、その関数のローカル変数(int x など)がスタックに積まれ、関数を抜けると自動でまとめて片付けられます。速くて手間いらずですが、「関数を抜けると消える」「大きさは前もって決まっている」という制約があります。第4章で学んだローカル変数は、すべてこのスタック上にあります。
flowchart TB m["main() の枠<br/>ローカル変数"] --> f["func() を呼ぶと<br/>上に積まれる"] --> g["さらに呼ぶと<br/>また積まれる"] g -.->|"関数を抜けると上から順に片付く"| m
ヒープ (heap) は、プログラマが「今これだけ必要」と明示して借りる、大きくて自由な領域です。C++ では new で借り、delete で返します。借りたメモリの場所は住所(ポインタ)で受け取ります。だから、ヒープを扱うにはポインタが欠かせません。
// ※この例は手元の本物のコンパイラで実行してください
#include <iostream>
using namespace std;
int main() {
int *p = new int; // ヒープから int 1個ぶんを借り、その住所を p に受け取る
*p = 42; // 借りた場所に値を書く
cout << *p << endl; // 42
delete p; // 借りたメモリを返す(返し忘れ=メモリリーク)
return 0;
}
スタックとヒープの違いを整理します。
| スタック | ヒープ | |
|---|---|---|
| 確保・解放 | 自動(関数の出入りで) | 手動(new / delete) |
| 寿命 | 関数を抜けると消える | delete するまで残る |
| 速さ | とても速い | やや遅い |
| 大きさ | 小さめ・固定的 | 大きく取れる・可変 |
| 使いどころ | 一時的なローカル変数 | 実行中に大きさが決まるデータ |
なぜヒープが必要なのでしょうか。「実行してみないと必要な個数が分からない」データ(利用者が入力した件数ぶんの配列など)は、スタックの固定サイズでは足りません。そこで、実行中に必要なだけヒープから借りるのです。ただし借りたら必ず返す(delete)のがルールで、返し忘れると使わないメモリが増え続ける メモリリーク になります。
flowchart LR A["new で借りる"] --> B["ポインタで使う<br/>*p = ..."] --> C["delete で返す"] B -.->|"返し忘れると"| D["メモリリーク<br/>(無駄が増え続ける)"]
まとめ
- メモリは大きくスタック(自動・一時的)とヒープ(手動・自由)に分かれる。
- ローカル変数はスタック、
newで借りるのはヒープ。 - ヒープの場所はポインタで受け取って使う――これがポインタの最も重要な役割のひとつ。
- 借りたら返す(
delete)。返し忘れはメモリリーク。
現代の C++ では、delete の返し忘れを防ぐために std::vector(次章)やスマートポインタといった安全な道具が用意されています。まずは「メモリには住所があり、ポインタでそれを扱う」という土台をつかめれば十分です。ここで学んだ考え方は、他の言語(Java・C#・Python など)の「参照」や「ガベージコレクション」を理解するときにも必ず役立ちます。