コース一覧
    C言語中級 連結リスト・応用
    メモリバグ演習

    C言語中級 連結リスト・応用

    C言語の連結リストと応用的なデータ操作を学び、実装でデータを扱うコースです。

    1
    連結リスト
    01. 配列の限界15分
    02. ノードを定義する15分
    03. 先頭に追加する15分
    04. リストを歩く15分
    05. 任意位置の挿入と削除15分
    06. リストを全解放する15分
    07. つくる リスト版成績管理15分
    08. 第5章クイズ10分
    2
    応用
    01. 動的な二次元配列15分
    02. コマンドライン引数15分
    03. バイナリ保存15分
    04. エラーの調べ方15分
    05. つくる 引数とバイナリ保存15分
    06. 第6章クイズ10分
    3
    総合制作
    01. リスト版を完成させる20分
    02. メモリバグ演習20分
    03. 自由拡張20分
    04. 完成と次のステップ10分

    メモリバグ演習

    動いているのに壊れているコード

    今回は自分でゼロから書きません。すでに動く成績管理CLI が渡されていて、そこに 3 種類のメモリバグ が仕込まれています。あなたの仕事は、それを見つけて直すことです。

    メモリのバグがやっかいなのは、その場では落ちない ことです。文法の間違いはコンパイラが止めてくれますし、ゼロ除算はその行で落ちます。ところが解放し忘れは何も起こさずに進みますし、解放済みの領域を触っても、たまたま中身が残っていればしばらく正しく動いてしまいます。落ちるのは何万回か動かしたあと、しかも原因とまったく関係ない場所です。

    数えれば見える

    このコースでは valgrind のような検査ツールが使えないので、自分で数える道具 を最初から用意してあります。my_malloc と my_free という薄い包みで、確保した領域のアドレスを配列に控えておき、確保と解放の回数を数えます。プログラムの最後に 確保4回 解放4回 のように出るので、数が合わなければどこかで解放し忘れています。

    my_free はもう 1 つ仕事をします。渡されたアドレスが控えの中に見つからなければ、それは すでに解放した領域か、そもそも確保していない領域 です。その場合は本物の free を呼ばずに警告を出します。実際の free を二重に呼ぶとプログラムはその場で異常終了しますが、この包みを通しておけば、二重解放が起きたという事実だけが 1 行の出力として残ります。

    解放してもポインタは元のまま

    3 つめのバグは、リストを全解放したあとの head です。free に渡すのはポインタの 値 であって、変数そのものではありません。ですから free_all(head) を呼んでも head の中身は書き換わらず、もう存在しないノードの番地を指し続けます。これが ダングリングポインタ です。

    こわいのは if (head != NULL) という見慣れた確認が通ってしまうことです。中身は解放済みなので、この判定に従って走査すれば何が起きるか分かりません。解放したら、その領域を指していた変数に NULL を入れる。これが習慣として要る理由がここにあります。

    3つのバグの見つけ方

    リーク は数字で見つかります。確保の回数と解放の回数が合わなければ、どこかで解放していません。ノード 1 つにつき確保は 2 回起きているので、捨てる側も 2 回でなければ釣り合いません。片方だけしか書いていない場所を探します。

    二重解放 は警告の行で分かります。ただし警告が出た時点ではもう遅く、原因は「同じ領域を解放する行が 2 か所に散らばっていること」です。よくあるのは、後始末を関数にまとめたのに、呼び出す側にも以前の解放が残っている形です。今回のコードもその形になっています。

    ダングリング は出力からは直接見えません。全解放のあとに何をしているかを読んで、もう存在しない領域を指したままの変数を探します。

    直す順番

    まず一度そのまま動かして、出力がどう崩れているかを読みます。次に確保と解放の回数が合わない原因を探し、最後に警告が出た行をたどります。3 か所とも、直すのは 1 行から 2 行です。原因を見つけるまでが長く、直すのは一瞬という比率も、実際のデバッグとよく似ています。

    要件

    1. my_malloc と my_free と確保・解放の数え上げには手を入れない。バグはリストを扱う側にある
    2. 1 は名前と点数を読んで末尾に追加し「田中を登録しました」と出す。2 は登録順に「田中 80点」を並べる。0人なら「学生がいません」
    3. 3 は名前を読んで一致するノードを外し「田中を削除しました」と出す。無ければ「田中は見つかりません」
    4. 終了時は残ったノードを全部解放し、「リストは空です」「確保4回 解放4回」「終了します」の3行を出す
    5. 「解放済みの領域をもう一度解放しました」が1度も出ないようにする
    6. 確保の回数と解放の回数を必ず一致させる。削除した学生の名前ぶんも解放する
    7. 全解放したあとの head を、もう使えない番地を指したままにしない

    ヒント

    ノード1つにつき確保は2回です。ノード本体と名前で、捨てるときも2回解放が要ります。delete_by_name をよく読んでください

    同じ領域を解放する行が2か所に分かれていないか探します。destroy_node が何をしているかを先に確かめると見つかります

    free は渡された番地を返すだけで、呼び出し側の変数は書き換えません。解放したあとの変数は自分で NULL にします

    生田 陸人
    監修生田 陸人
    ゆめさくエンジニア / 現役ソフトウェアエンジニア監修者プロフィールを見る →
    編集 ゆめさく編集部·公開 2026/08/07

    関連レッスン

    • 自由拡張

      欲しい機能を自分で設計して足せるようになります。

    • 完成と次のステップ

      次に何を学ぶかを選べるようになります。

    • リスト版を完成させる

      全機能をリスト前提で1本にまとめられるようになります。

    このレッスンに出てくる用語

    意味があいまいなまま進んだ語は、ここから読み直せます。

    • メモリプログラムとデータを一時保持する高速領域
    • 配列サイズ固定の同型データの集まり
    • リスト順序付きで複数の値を扱うデータ構造
    • 変数データに名前をつけて参照する仕組み
    • 関数処理に名前を付けて再利用できる単位
    • デバッグバグを見つけて直す調査と修正の作業
    main.c
    エディタを読み込んでいます

    メモ

    メモリバグ演習

    ⌘S で保存