スタックとヒープは別の場所
C言語の変数は、スタックかヒープのどちらかに置かれます。スタックは関数を呼ぶたびに自動で増え、ヒープは malloc で自分で確保して、free で自分で返す場所です。
どちらに置くかで、変数が生きる長さと、起こしやすい誤りが変わります。メモリリークと解放後の使用は、どちらもヒープの誤りです。
このレッスンのゴール
メモ
変数がどこにあるかと、mallocとfreeの働きを言える。
重要な言葉
| ことば | いみ |
|---|---|
| スタック | 関数を呼ぶたびに、自動で増減する場所 |
| スタックフレーム | 1回の関数呼び出しが使う、スタックの区切り |
| ヒープ | 自分で確保し、自分で返すメモリの場所 |
| malloc | ヒープからバイト数を確保し、番地を返す関数 |
| メモリリーク | 返し忘れて、使えない確保が溜まること |
| 解放後の使用 | free したメモリを、あとで使ってしまう誤り |
学生が入力するセルは、このプレビューでは空白です。
ローカル変数は関数が終わると消える
関数のローカル変数は、スタックに置かれます。関数を呼ぶと、その分の区切りが積まれ、関数が終わると取り除かれます。
取り除かれた場所の番地を返しても、中身は保証されません。関数の外では、その番地を使ってはいけません。
fact(3) を呼んだときのスタック
| 深さ | 呼ばれた関数 | 残っているもの |
|---|---|---|
| 1 | main | 自分の変数 |
| 2 | fact(3) | n は3。結果を待っている |
| 3 | fact(2) | n は2。結果を待っている |
| 4 | fact(1) | n は1。1を返して終わる |
学生が入力するセルは、このプレビューでは空白です。
5回の呼び出しで階乗を求める
printf '%s\n' 'int fact(int n){if (n <= 1) return 1; return n * fact(n - 1);} int main(void){printf("%d\n", fact(5)); return 0;}' | gcc -include stdio.h -x c - -o /tmp/fact && /tmp/factここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ローカル変数の番地を返すと警告が出る
printf '%s\n' 'int *make(void){int n = 5; return &n;}' | gcc -Wall -x c - -c -o /tmp/r.o 2>&1 | grep -o 'address of local variable'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
mallocで確保し、freeで返す
malloc(n) は、ヒープから n バイトを確保して、先頭の番地を返します。使い終わったら、free で返します。
返さずに番地を失うと、メモリリークです。返したあとに使うと、解放後の使用です。どちらも、動いているうちは気づきにくい誤りです。
ヒープの使い方の流れ
| 順 | 書き方 | 起きること |
|---|---|---|
| 1 | p = malloc(12) | 12バイトを確保して、番地を p に入れる |
| 2 | p[0] = 5 | 確保した場所に書く |
| 3 | free(p) | 場所を返す。p は古い番地を持ったまま |
| 4 | p = NULL | 使わないと決めて、印を付ける |
学生が入力するセルは、このプレビューでは空白です。
配列の大きさとポインタの大きさ
printf '%s\n' 'int main(void){int arr[10]; int *p = malloc(10 * sizeof(int)); printf("%zu %zu\n", sizeof(arr), sizeof(p)); free(p); return 0;}' | gcc -include stdio.h -include stdlib.h -x c - -o /tmp/hp && /tmp/hpここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ヒープの3つの int を足す
printf '%s\n' 'int main(void){int *p = malloc(3 * sizeof(int)); p[0] = 5; p[1] = 6; p[2] = 7; printf("%d\n", p[0] + p[1] + p[2]); free(p); return 0;}' | gcc -include stdio.h -include stdlib.h -x c - -o /tmp/hp2 && /tmp/hp2ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
callocは0で埋めて確保する
printf '%s\n' 'int main(void){int *p = calloc(4, sizeof(int)); printf("%d %d\n", p[0], p[3]); free(p); return 0;}' | gcc -include stdio.h -include stdlib.h -x c - -o /tmp/cal && /tmp/calここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
free をすると、ポインタが NULL になって安全、と思いがちです。free が返すのは場所だけで、p は古い番地を持ったままです。読み書きすると、解放後の使用になります。free のすぐあとに p = NULL; と書くと、誤りに気づきやすくなります。
さらに挑戦: 4バイト単位にそろえる
課題が通ったら、alloc が n を4の倍数に切り上げて確保するようにしましょう。5バイトを頼むと、8バイトの区画が使われます。
そして、5バイトを3回頼むと、空きが何バイト減るかを、自分のテストで確かめましょう。24バイトです。