私が読んでいたC++の本は、delete
演算子を使用してポインターを削除すると、ポインターが指している場所のメモリーは「解放」され、上書きできると述べています。また、ポインターが再割り当てされるか、NULL
に設定されるまで、ポインターは同じ場所を指し続けます。
ただし、Visual Studio 2012では。これは事実ではないようです!
例:
#include <iostream>
using namespace std;
int main()
{
int* ptr = new int;
cout << "ptr = " << ptr << endl;
delete ptr;
cout << "ptr = " << ptr << endl;
system("pause");
return 0;
}
このプログラムをコンパイルして実行すると、次の出力が得られます。
ptr = 0050BC10
ptr = 00008123
Press any key to continue....
削除が呼び出されると、ポインターが指しているアドレスが明らかに変化します!
なぜこうなった?これは特にVisual Studioと関係がありますか?
とにかく削除が指しているアドレスを変更できる場合、なぜ削除しないのは自動的にポインタをランダムなアドレスの代わりにNULL
に設定しないのですか?
ptr
に保存されているアドレスは、常に00008123
で上書きされていることに気付きました...
これは奇妙に思えたので、少し掘り下げて、これを見つけました Microsoftブログ投稿 「C++オブジェクトを削除するときの自動化されたポインターのサニタイズ」に関するセクションが含まれています。
... NULLのチェックは一般的なコード構成であり、既存のNULLのチェックとサニタイズ値としてのNULLの併用は、根本的な原因に対処する必要のある真のメモリ安全性の問題を偶然隠してしまう可能性があることを意味します。
このため、サニタイズ値として0x8123を選択しました。オペレーティングシステムの観点からは、これはゼロアドレス(NULL)と同じメモリページにありますが、0x8123でのアクセス違反は、より詳細な注意が必要なため、開発者にとってより目立ちます。
ポインターが削除された後、Visual Studioがポインターで何をするかを説明するだけでなく、自動的にNULL
に設定しないことを選択した理由にも答えます。
この「機能」は、「SDLチェック」設定の一部として有効になります。有効/無効にするには:PROJECT-> Properties-> Configuration Properties-> C/C++-> General-> SDL Checks
これを確認するには:
この設定を変更して同じコードを再実行すると、次の出力が生成されます。
ptr = 007CBC10
ptr = 007CBC10
「機能」は引用符で囲まれています。同じ場所への2つのポインターがある場合、deleteを呼び出すと、それらの1つだけがサニタイズされる[〜#〜] one [〜#〜] 。もう1つは、無効な場所を指しているままになります。
Visual Studioは、設計上のこの欠陥を文書化しないと、厄介な状況に陥る可能性があります。
/sdl
コンパイルオプションの副作用があります。 VS2015プロジェクトではデフォルトでオンになっており、/ gsで提供されるもの以外の追加のセキュリティチェックを有効にします。 [プロジェクト]> [プロパティ]> [C/C++]> [一般]> [SDLチェック]設定を使用して変更します。
MSDN記事 からの引用:
- 限定されたポインターのサニタイズを実行します。間接参照を含まない式およびユーザー定義のデストラクタを持たない型では、deleteの呼び出し後にポインター参照が無効なアドレスに設定されます。これは、古いポインター参照の再利用を防ぐのに役立ちます。
MSVCを使用する場合、削除されたポインターをNULLに設定することは悪い習慣であることに注意してください。デバッグヒープとこの/ sdlオプションの両方から得られるヘルプが無効になり、プログラム内の無効なfree/delete呼び出しを検出できなくなります。
また、ポインタが再割り当てされるか、NULLに設定されるまで、ポインタは同じ場所を指し続けます。
それは間違いなく誤解を招く情報です。
削除が呼び出されると、ポインターが指しているアドレスが明らかに変化します!
なぜこうなった?これは特にVisual Studioと関係がありますか?
これは明らかに言語仕様の範囲内です。 ptr
は、delete
の呼び出し後は無効です。 ptr
dの後にdelete
を使用すると、未定義の動作の原因になります。 それをしないでください。ランタイム環境は、ptr
の呼び出し後、delete
を使用して自由に実行できます。
とにかく削除がそれが指しているアドレスを変更できるなら、なぜ削除しないのは自動的にポインターをいくつかのランダムなアドレスの代わりにNULLに設定しますか?
ポインターの値を古い値に変更することは、言語仕様の範囲内です。 NULLに変更する限り、それは悪いことだと思います。ポインターの値がNULLに設定されている場合、プログラムはより健全な方法で動作します。しかし、それは問題を隠します。プログラムが異なる最適化設定でコンパイルされているか、異なる環境に移植されている場合、問題は最も不適切な瞬間に現れる可能性があります。
何らかのデバッグモードを実行しており、VSがポインターを既知の場所に再ポイントしようとしているため、それをさらに参照解除しようとすると、トレースおよび報告される可能性があります。リリースモードで同じプログラムをコンパイル/実行してみてください。
通常、ポインターは、効率のために、および安全性について誤った考えを与えることを避けるために、delete
内で変更されません。削除されたポインターはこの場所を指しているいくつかのポインターの1つに過ぎない可能性が高いため、削除ポインターを事前定義された値に設定しても、ほとんどの複雑なシナリオでは役に立ちません。
実際のところ、私がそれについて考えるほど、VSがそうするとき、いつものように過失に気づくことが多くなります。ポインターがconstの場合はどうなりますか?まだ変更するつもりですか?
ポインターを削除した後、ポインターが指すメモリーはまだ有効である可能性があります。このエラーを明示するために、ポインター値は明白な値に設定されます。これはデバッグプロセスを本当に助けます。値がNULL
に設定されている場合、プログラムフローで潜在的なバグとして表示されることはありません。そのため、後でNULL
に対してテストするときにバグを隠すことができます。
もう1つのポイントは、一部のランタイムオプティマイザーがその値をチェックし、その結果を変更する可能性があることです。
以前は、MSは値を0xcfffffff
に設定していました。