Code Fix

上級

free()した後のポインタを使っても動いてしまう(use-after-free)

free()で解放したメモリに対して読み書きを続けても、多くの場合その場ではクラッシュせず、直前まで入っていた値がそのまま読めてしまいます。

なぜエラーが出ないのか

出力: 42とは無関係な値(実行のたびに変わる)
(エラーなし)
Cは何も報告しません。コンパイルも実行も正常に完了するためです
出力: 42とは無関係な値(実行のたびに変わる)
実際の挙動 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  #include <stdio.h>
 2  #include <stdlib.h>
 3  
 4  int main(void) {
 5      int *p = malloc(sizeof(int));
 6      *p = 42;
 7      free(p);         
                ^
 8      printf("%d\n", *p);
 9      return 0;
10  }
出力: 42とは無関係な値(実行のたびに変わる)期待: 42

free()は解放した領域の中身をその場で消しません。ただし多くの実装では、解放した領域の先頭に管理用のポインタ(次に再利用する空き領域への情報)を書き込みます。そのため、free()の直後に読むだけでも書き込んだ値(42)ではなく無関係な値が出ることがあり、しかも実行のたびに変わります。

直し方: free(p);(空) にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  #include <stdio.h>
 2  #include <stdlib.h>
 3  
 4  int main(void) {
 5      int *score = malloc(sizeof(int));
 6      *score = 88;
 7      free(score);             
                    ^
 8      printf("%d\n", *score);
 9      return 0;
10  }
出力: 88とは無関係な値(実行のたびに変わる)期待: 88

クラッシュしないため一見正しく動いているように見えますが、free()した時点でその領域はもう自分のものではありません。読み取った値をそのまま信用してはいけません。

直し方: free(score);(空) にします。

この問題を解いてみる →

パターン3

 1  #include <stdio.h>
 2  #include <stdlib.h>
 3  
 4  int main(void) {
 5      int *counter = malloc(sizeof(int));
 6      *counter = 7;
 7      free(counter);               
                      ^
 8      printf("%d\n", *counter);
 9      return 0;
10  }
出力: 7とは無関係な値(実行のたびに変わる)期待: 7

エラーにもならず、値が変わっているのに気づきにくいのがuse-after-freeの怖さです。解放したポインタは二度と参照しないのが鉄則です。

直し方: free(counter);(空) にします。

この問題を解いてみる →

よくある誤解

「クラッシュしないなら安全」ではありません。解放した領域は次のmalloc()で別の目的のために再利用される可能性があり、その瞬間に無関係なデータが化けたように見えるバグになります。クラッシュしないからこそ発見が遅れる、Cで最も厄介な種類の不具合の一つです。

まとめ

free()した後のポインタを使っても動いてしまう(use-after-free)は上級でつまずきやすい項目です。上の3パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)