第03問の答え

答え

malloc()free()を呼び出す回数が違う!
  for (int i=0; i<strlen(text); i++) {
    pPartOfText = malloc(sizeof(text)); /* ← 繰り返し呼び出される */
    if (pPartOfText) {
      strncpy(pPartOfText, text + i, sizeof(text));
      printf("%d: %s\n", i, pPartOfText);
    }
  }

  if (pPartOfText) {
    free(pPartOfText); /* ← 1度しか呼び出されない */
    pPartOfText = 0;
  }
malloc()free()の呼び出し回数を揃えるのが正解!
  for (int i=0; i<strlen(text); i++) {
    pPartOfText = malloc(sizeof(text));
    if (pPartOfText) {
      strncpy(pPartOfText, text + i, sizeof(text));
      printf("%d: %s\n", i, pPartOfText);

      free(pPartOfText);
      pPartOfText = 0;
    }
  }

解説

これは、メモリーリークに関する出題でした。メモリーリークとは、不要になったメモリーを解放せずに放置してしまうことです。

問題のプログラムではmalloc()forループの内側にあり、全部で6回呼び出されています。これに対して、free()は高々1回しか呼ばれません。これでは、5つのメモリー領域が解放されずに残ってしまいます。

それでも、今回のプログラムを実行すると期待通りの内容が表示されました。メモリーリークは、発生したからといってすぐに致命的な問題を引き起こすわけではないのです。でも、こういう小さな見落としの積み重ねが、やがて大きな問題につながります。

だからmalloc()で確保したメモリーの数だけfree()が呼び出されるように気を付けないと危険なのよ。
そんなこと言われても、どうやって気を付けたらいいのか……。
そうね。今回のプログラムの場合は、ポインタを使い回してるところが良くないわね。

問題のプログラムでは、ループの内側でmalloc()の結果を受け取るポインタが、ループの外側で宣言されていました。1つのポインタを何度も使い回しています。

  char *pPartOfText = 0;
  for (int i=0; i<strlen(text); i++) {
    pPartOfText = malloc(sizeof(text));

ポインタの宣言もループの内側に入れてしまえば、よりシンプルで見通しの良いプログラムになります。

  for (int i=0; i<strlen(text); i++) {
    char *pPartOfText = malloc(sizeof(text));
こうすれば、ループの外側にはfree()を書けなくなるでしょう?
え?……そっか、ポインタはループの内側にあるから、コンパイルエラーになっちゃいますね。
だからmalloc()free()も、必然的にループの内側に書くことになるわけ。
なるほど、それなら呼び出し回数も同じになりますね!

なお、malloc()で確保されたメモリー領域は、プログラムが実行を終えるときにすべて解放されます。そのため、今回のような小さなプログラムでは大した問題は起こりません。でも、プログラムが複雑になってくると、メモリーリークは致命的な問題につながります。

例えば、常駐型のプログラムが毎日少しずつfree()を呼び出し忘れていたらどうなるでしょうか。動作を開始してから数週間後にメモリー不足になり、突然クラッシュしてしまうかもしれません。

ここがポイント!
変数の使い回しを避けて、プログラムをシンプルにしよう!

修正後のプログラム

main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
  char text[] = "Hello!";

  for (int i=0; i<strlen(text); i++) {
    char *pPartOfText = malloc(sizeof(text)); /* ← ループの内側で宣言 */
    if (pPartOfText) {
      strncpy(pPartOfText, text + i, sizeof(text));
      printf("%d: %s\n", i, pPartOfText);

      free(pPartOfText); /* ← 確保されたメモリー領域の数だけ呼び出す */
      pPartOfText = 0;
    }
  }

  return EXIT_SUCCESS;
}
実行結果
0: Hello!
1: ello!
2: llo!
3: lo!
4: o!
5: !