メモリの地図
プログラムが使う3つの置き場所
第1章では、ポインタが「値そのものではなく、値の置き場所を指すもの」だと確かめました。ここからは、その置き場所がどこにあるのかを見ていきます。
実行中のCプログラムは、メモリを用途ごとに分けて使っています。よく使うのは次の3つです。
- 静的領域 — グローバル変数と
staticを付けた変数が置かれる。プログラムが始まってから終わるまでずっと同じ場所にある - スタック — 関数のローカル変数と引数が置かれる。関数を呼ぶと積まれ、戻ると降ろされる
- ヒープ —
mallocで自分から要求したときだけ使える。返すまで生き続ける
この3つの違いは、大きさでも速さでもなく、いつ生まれていつ消えるかという寿命の違いです。ここを取り違えると、すでに消えた場所を読み書きしてしまい、Cで最も追いにくいバグになります。
静的領域はずっとある
グローバル変数は、プログラムが起動した時点で用意され、終了まで残ります。関数の中で static int called = 0; と書いた変数も同じで、置き場所は静的領域です。名前は関数の中でしか使えませんが、値は関数を抜けても残ったままになります。
void f(void) {
static int called = 0; /* 静的領域。前回の値が残る */
int local = 0; /* スタック。呼ぶたびに作り直される */
called++;
local++;
}static 付きの初期化 = 0 は、呼ばれるたびに実行されるのではなく、プログラム開始時に1度だけ行われます。だから2回目の呼び出しでは0に戻りません。
スタックは呼び出しのあいだだけ
対して local は関数に入るたびに新しく作られ、抜けると無くなります。ですから何回呼んでも local は1のままです。この作り直しがあるおかげで、同じ関数を再帰で何重に呼んでも変数が混ざりません。
ヒープは自分で始まりと終わりを決める
3つ目のヒープは、この章の後半で malloc を覚えてから使います。静的領域は寿命が長すぎ、スタックは短すぎるという場面、たとえば「実行してみるまで何人分要るか分からない」ときに使う置き場所です。
固定長の配列では、宣言の時点で要素数を決めなければなりません。100人分と書けば10人のときに無駄になり、1000人来たら足りません。この「実行するまで決まらない量」を扱えるのがヒープです。
どれがどこに置かれるか
第1章で扱った文字列リテラルも思い出してください。char *s = "田中"; の "田中" 本体は書き換えできない読み取り専用の領域に置かれ、s というポインタ変数だけがスタックにあります。同じ1行の中でも、置き場所が2つに分かれています。
| 書き方 | 置き場所 | 寿命 |
|---|---|---|
int total; を関数の外に書く | 静的領域 | 開始から終了まで |
関数の中の static int n; | 静的領域 | 開始から終了まで |
関数の中の int n; | スタック | 関数を抜けるまで |
malloc で確保した領域 | ヒープ | free するまで |
番地ではなく寿命で確かめる
メモリの説明ではよくアドレスを表示して並びを見せますが、実際の番地はOSや実行のたびに変わるので、覚えても意味がありません。見るべきなのは寿命です。今回は、同じ関数を3回呼んで、値が残る変数と毎回0に戻る変数を並べて表示し、置き場所の違いを出力の形で確かめます。
要件
- 呼ばれた回数は関数の中の static 変数で数える
- 毎回作り直される変数は普通のローカル変数にする
- 合計はグローバル変数 g_total に足していく
- 出力は called=1 local=1 total=80 の形で1行ずつ
入出力例
main("") → "called=1 local=1 total=80
called=2 local=1 total=170
called=3 local=1 total=240"