クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

スタックとヒープ、mallocとfree

このレッスンのあとで: 変数がどこにあるかと、mallocとfreeの働きを言える。

55分のレッスンコマンド5件コンパイラとアーキテクチャのラボ:gcc、flex、bison、RISC-V を1台の VM で

スタックとヒープは別の場所

C言語の変数は、スタックかヒープのどちらかに置かれます。スタックは関数を呼ぶたびに自動で増え、ヒープは malloc で自分で確保して、free で自分で返す場所です。

どちらに置くかで、変数が生きる長さと、起こしやすい誤りが変わります。メモリリークと解放後の使用は、どちらもヒープの誤りです。

このレッスンのゴール

メモ

変数がどこにあるかと、mallocとfreeの働きを言える。

重要な言葉

ことばいみ
スタック関数を呼ぶたびに、自動で増減する場所
スタックフレーム1回の関数呼び出しが使う、スタックの区切り
ヒープ自分で確保し、自分で返すメモリの場所
mallocヒープからバイト数を確保し、番地を返す関数
メモリリーク返し忘れて、使えない確保が溜まること
解放後の使用free したメモリを、あとで使ってしまう誤り

学生が入力するセルは、このプレビューでは空白です。

ローカル変数は関数が終わると消える

関数のローカル変数は、スタックに置かれます。関数を呼ぶと、その分の区切りが積まれ、関数が終わると取り除かれます。

取り除かれた場所の番地を返しても、中身は保証されません。関数の外では、その番地を使ってはいけません。

fact(3) を呼んだときのスタック

深さ呼ばれた関数残っているもの
1main自分の変数
2fact(3)n は3。結果を待っている
3fact(2)n は2。結果を待っている
4fact(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 で返します。

返さずに番地を失うと、メモリリークです。返したあとに使うと、解放後の使用です。どちらも、動いているうちは気づきにくい誤りです。

ヒープの使い方の流れ

順書き方起きること
1p = malloc(12)12バイトを確保して、番地を p に入れる
2p[0] = 5確保した場所に書く
3free(p)場所を返す。p は古い番地を持ったまま
4p = 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バイトです。

このラボとソースについて: コンパイラとアーキテクチャのラボ:gcc、flex、bison、RISC-V を1台の VM で

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。