Code Fix

上級

delete後のポインタ使用が「たまたま動いてしまう」危険性

deleteした直後の同じポインタを使って読み書きしてしまうバグです。未定義動作であるにもかかわらず、解放直後のメモリはすぐには再利用されないことが多く、正しい値が読めてしまうことがあります。

なぜエラーが出ないのか

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

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

パターン1

 1  #include <iostream>
 2  int main() {
 3      int *p = new int(42);
 4      delete p;
 5      std::cout << *p << std::endl;    
                        ^
 6      return 0;
 7  }
出力: 実行のたびに変わりうる無関係な値(クラッシュはしない)危険性: 未定義動作。たまたま動いているだけ

deleteは確保していた領域を「他の用途に使ってよい」と管理システムに伝えるだけで、中身を即座に上書きするとは限りません。解放直後のメモリはすぐには再利用されないことが多く、たまたま動いてしまうことがあります。

直し方: std::cout << *p << std::endl;std::cout << "done" << std::endl; にします。

この問題を解いてみる →

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

パターン2

 1  #include <iostream>
 2  int main() {
 3      int *value = new int(100);
 4      delete value;
 5      std::cout << *value << std::endl;    
                          ^
 6      return 0;
 7  }
出力: 実行のたびに変わりうる無関係な値(クラッシュはしない)危険性: 未定義動作。たまたま動いているだけ

「エラーなく動いたのだから解放は間違っていなかった」というのは誤解です。プログラムが複雑になり別の場所で確保・解放が起きるようになると、突然壊れた値が現れることがあります。

直し方: std::cout << *value << std::endl;std::cout << "finished" << std::endl; にします。

この問題を解いてみる →

パターン3

 1  #include <iostream>
 2  int main() {
 3      int *n = new int(7);
 4      delete n;
 5      std::cout << *n << std::endl;  
                       ^
 6      return 0;
 7  }
出力: 実行のたびに変わりうる無関係な値(クラッシュはしない)危険性: 未定義動作。たまたま動いているだけ

delete後のポインタを一切使わない、または直後にnullptrを入れておくのが安全な習慣です。使ってしまっている行自体を削除するのが最も確実な直し方です。

直し方: std::cout << *n << std::endl;std::cout << "ok" << std::endl; にします。

この問題を解いてみる →

よくある誤解

「エラーなく動いたのだから解放は間違っていなかった」というのは誤解です。deleteは確保していた領域を「他の用途に使ってよい」と管理システムに伝えるだけで、その領域の中身自体を即座に上書きするとは限りません。プログラムが複雑になり別の場所で確保・解放が起きるようになると、突然壊れた値が現れることがあります。

まとめ

delete後のポインタ使用が「たまたま動いてしまう」危険性は上級でつまずきやすい項目です。上の3パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

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