第02問の答え

答え

free()printf()の順番が間違い!
  if (pCopyOfText) {
    strncpy(pCopyOfText, text, sizeof(text));
    free(pCopyOfText);
  }

  printf("%s\n", pCopyOfText);
printf()free()の前に実行するのが正解!
  if (pCopyOfText) {
    strncpy(pCopyOfText, text, sizeof(text));
    printf("%s\n", pCopyOfText);

    free(pCopyOfText);
  }

解説

これは、ダングリングポインタに関する出題でした。ダングリング(dangling)は「宙ぶらりんの」という意味で、ポインタが有効なアドレスを指していない状態を表しています。

だいたいの場合、ダングリングポインタはメモリーを解放するタイミングで発生します。今回のプログラムでは、malloc()で確保したメモリーをfree()で解放していますね。このとき、ポインタが解放済みの領域を指したままになっています。

このポインタを、そのままprintf()のところで使おうとしているのが問題です。もう無効になったアドレスを指しているため、何が起こるか分かりません。たまたま期待どおりの結果が表示されるかもしれないし、おかしな値が表示されるかもしれないのです。最悪の場合は、プログラム自体がクラッシュします。

解放済みのメモリーは、うっかり使わないように気を付けないと危険ってことね。
それって、気を付けようがないんじゃ……。
大丈夫。ポインタをきちんとクリアするクセを付ければいいのよ。

free()でメモリーを解放したあと、そのメモリーを指すポインタを使い続けるのは危険です。それなら、ポインタの値をクリアしてしまいましょう。

    free(pCopyOfText);
    pCopyOfText = 0;

こうしておけば、ポインタが「宙ぶらりん」になるのは一瞬だけです。クリア済みのポインタは、うっかり使ったとしても確実にクラッシュさせることができます。

ええっ!クラッシュしないほうがいいんじゃないんですか?
それはそうよ。でも、バグがあるのにうまく動いてしまったら、なかなか問題に気付けないでしょう?
あ、もしかして……。クラッシュさせたほうがバグに気付きやすくなるってことですか?
そう!そうしたら早めに修正できるから、最終的にはクラッシュしにくいプログラムになるわね。
そういうことか〜。
ここがポイント!
メモリーを解放したら、ポインタも一緒にクリアするクセを付けよう!

修正後のプログラム

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

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

  char *pCopyOfText = malloc(sizeof(text));
  if (pCopyOfText) {
    strncpy(pCopyOfText, text, sizeof(text));
    printf("%s\n", pCopyOfText); /* ← この行を修正 */

    free(pCopyOfText);
    pCopyOfText = 0; /* ← ポインタをクリア */
  }

  return EXIT_SUCCESS;
}
実行結果
Hello!