リストを全解放する
1回の free ではリストは片付かない
動的配列なら、確保したのは大きな1枚なので free(list) の一発で終わりでした。リストは違います。ノードは1個ずつ別々に malloc した、別々の領域です。100人いれば100回の free が要ります。
そして、ここには順番の罠があります。
while (head != NULL) {
free(head);
head = head->next; /* 解放済みの領域を読んでいる */
}free(head) を呼んだ時点で、その領域はもう自分のものではありません。head->next はそこにある値を読む操作なので、返した領域を読むことになります。多くの環境では値が残っていて動いてしまいますが、動くことと正しいことは別です。処理系がその領域を管理用に使い回した瞬間、次のノードとは無関係な値が入り、リストの途中で落ちるか、知らない番地を解放しようとします。
次を控えてから解放する
正しい形はこうです。
int free_list(struct Node *head) {
struct Node *next;
int freed = 0;
while (head != NULL) {
next = head->next;
free(head);
freed++;
head = next;
}
return freed;
}解放する前に、次の行き先を別の変数へ控えます。手すりから手を離す前に、次の手すりを掴んでおく、という順序です。
解放したあとのポインタ
free はポインタ変数の中身を消しません。渡した番地はそのまま残っていて、しかしその先はもう他人のものです。これをダングリングポインタと呼びます。呼び出し元では、リストを空にしたあと head に NULL を入れておきます。
free_list(head);
head = NULL;NULL を入れておく利点は2つあります。うっかり使ったときに読み書きではなくその場で落ちるので原因が近くで分かること、そして free(NULL) は何もしないと決められているので、二重解放の事故が起きなくなることです。
誰がいつ解放するのか
リストのノードを作ったのは insert_head ですが、解放の責任を負うのはリストを持っている側です。関数が確保して呼び出し元が解放する形は珍しくなく、そのときは「この関数が返したものは呼び出し元が free する」という約束を、名前かコメントで残しておきます。約束が書かれていないヒープの受け渡しが、リークの発生源になります。
では、解放した個数を数えながらリストを片付けましょう。
要件
- 解放する前に次のノードの番地を別の変数に控える
- 全ノードを1つ残らず解放する
- 解放した個数を戻り値で返す
- リストが空のときは0を返す
入出力例
main("3", "田中 82", "鈴木 91", "佐藤 70") → "佐藤 70点
鈴木 91点
田中 82点
3個解放しました
headはNULLです"
main("0") → "0個解放しました
headはNULLです"