Visual Studioは削除されたポインターをどのように処理しますか、そしてその理由は何ですか?


130

私が読んでいる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....

明らかに、deleteが呼び出されると、ポインターが指しているアドレスが変更されます!

なぜこうなった?これは特にVisual Studioと関係がありますか?

そして、削除がとにかく指しているアドレスを変更できる場合、削除によって、NULLランダムなアドレスの代わりにポインタが自動的に設定されないのはなぜですか?


4
ポインタを削除しても、NULLに設定されるわけではないので、注意する必要があります。
マット、

11
私はそれを知っていますが、私が読んでいる本には、削除前に指していたのと同じアドレスがまだ含まれていると明確に書かれていますが、そのアドレスの内容は上書きされる可能性があります。
tjwrona1992、2015年

6
@ tjwrona1992、はい、これは通常起こっていることです。この本では、ハードルールではなく、最も可能性の高い結果がリストされています。
SergeyA、2015年

5
@ tjwrona1992 C ++の本を読んでいます -その本の名前は...?
PaulMcKenzie

4
@ tjwrona1992:驚くかもしれませんが、逆参照だけでなく、未定義の動作である無効なポインター値のすべての使用法です。「それが指している場所のチェック」は、許可されていない方法で値を使用しています。
Ben Voigt、2015年

回答:


175

に保存されているアドレスptrが常に上書きされていることに気づきました00008123...

これは奇妙に思えたので、少し掘り下げたところ、「Microsoftのブログ投稿に、C ++オブジェクトを削除する際のポインタのサニタイズを自動化する」についてのセクションが含まれていることがわかりました。

... NULLのチェックは一般的なコード構成です。つまり、既存のNULLのチェックとNULLをサニタイズ値として使用すると、根本的な原因が本当に対処する必要がある真のメモリ安全性の問題を偶然に隠すことができます。

このため、サニタイズ値として0x8123を選択しました。これは、オペレーティングシステムの観点からは、ゼロアドレス(NULL)と同じメモリページにありますが、0x8123でのアクセス違反は、より詳細な注意が必要なため、開発者にとってより目立ちます。 。

削除された後のVisual Studioのポインターの扱いを説明するだけでなく、ポインターをNULL自動的に設定しないことを選択した理由にも答えます!


この「機能」は、「SDLチェック」設定の一部として有効になります。有効化/無効化するには、プロジェクト->プロパティ->構成プロパティ-> C / C ++->一般-> SDLチェックに移動します。

これを確認するには:

この設定を変更して同じコードを再実行すると、次の出力が生成されます。

ptr = 007CBC10
ptr = 007CBC10

「機能」は削除させていただきます唯一のサニタイズ呼び出し、なぜならあなたは同じ場所に二つのポインタを持っている場合は、引用符で囲まれているONEそれらのを。もう1つは無効な場所を指したままになります...


更新:

さらに5年間のC ++プログラミングの経験を経て、この問題全体が基本的に問題点であることに気づきました。C ++プログラマーであり、スマートポインター(この問題全体を回避する)の代わりにrawポインターを引き続き使用newdeleteて管理している場合は、Cプログラマーになるためのキャリアパスの変更を検討することをお勧めします。;)


12
それは素晴らしい発見です。MSがこのようなデバッグ動作を文書化したほうがいいと思います。たとえば、これを実装し始めたコンパイラのバージョンと、動作を有効/無効にするオプションを知っておくとよいでしょう。
Michael Burr

5
「オペレーティングシステムの観点からは、これはゼロアドレスと同じメモリページにあります」-え?x86の標準(ラージページを無視する)ページサイズは、ウィンドウとLinuxの両方で4kbのままではないですか Raymond Chenのブログのアドレススペースの最初の64 kbについて少し覚えているので、実際には同じ結果になります
Voo

12
@Vooウィンドウは、最初(および最後)の64kB相当のRAMをトラップ用のデッドスペースとして予約します。0x8123はうまくそこに落ちる
ラチェットフリーク

7
実際、これは悪い習慣を助長するものではなく、ポインタをNULLに設定することをスキップすることもできません。それが、の0x8123代わりに使用している理由です0。ポインタは依然として無効ですが、それを逆参照しようとすると例外が発生し(正常)、NULLチェックに合格しません(これを行わないとエラーになるため、正常です)。悪い習慣の場所はどこですか?これは本当にデバッグに役立つものです。
Luaan、2015年

3
まあ、それらの両方(すべて)を設定することはできないので、これは2番目に良いオプションです。気に入らない場合は、SDLチェックをオフにしてください。他の人のコードをデバッグする場合は特に便利です。
Luaan、2015年

30

/sdlコンパイルオプションの副作用が表示されます。VS2015プロジェクトのデフォルトでオンになっているため、/ gsによって提供されるもの以外の追加のセキュリティチェックが可能になります。プロジェクト>プロパティ> C / C ++>一般> SDLチェック設定を使用して変更します。

MSDNの記事からの引用:

  • 限られたポインターのサニタイズを実行します。逆参照を含まない式、およびユーザー定義のデストラクタを持たない型では、deleteの呼び出し後にポインタ参照が無効なアドレスに設定されます。これは、古いポインタ参照の再利用を防ぐのに役立ちます。

MSVCを使用する場合、削除されたポインタをNULLに設定することは悪い習慣であることを覚えておいてください。デバッグヒープとこの/ sdlオプションの両方から得られるヘルプを無効にし、プログラムで無効な解放/削除呼び出しを検出できなくなります。


1
確認しました。この機能を無効にすると、ポインターはリダイレクトされなくなります。それを変更する実際の設定を提供してくれてありがとう!
tjwrona1992、2015年

ハンス、2つのポインターが同じ場所を指している場合に、削除されたポインターをNULLに設定することは依然として悪い習慣と考えられていますか?するとdelete、Visual Studioは、2番目のポインターが、現在は無効である元の場所を指したままにします。
tjwrona1992

1
ポインタをNULLに設定することによって、どのような魔法が発生すると予想されるのか、私にはかなり不明確です。他のポインターはそうではないので、それは何も解決しません。バグを見つけるには、デバッグアロケーターが必要です。
Hans Passant、2015年

3
VSはないではないポインタをクリーンアップします。それはそれらを破壊します。したがって、とにかくそれらを使用すると、プログラムがクラッシュします。デバッグアロケーターは、ヒープメモリでもほとんど同じことを行います。NULLの大きな問題は、十分に破損していないことです。そうでなければ、一般的な戦略、グーグル「0xdeadbeef」。
Hans Passant、2015年

1
ポインタをNULLに設定することは、現在は無効である以前のアドレスをポイントしたままにするよりもはるかに優れています。NULLポインタに書き込もうとしても、データは破損せず、おそらくプログラムがクラッシュします。その時点でポインタを再利用しようとしても、プログラムがクラッシュすることはなく、非常に予測できない結果が生じる可能性があります。
tjwrona1992、2015年

19

また、ポインタが再度割り当てられるか、NULLに設定されるまで、ポインタは引き続き同じ場所を指すことを示しています。

これは間違いのある情報です。

明らかに、deleteが呼び出されると、ポインターが指しているアドレスが変更されます!

なぜこうなった?これは特にVisual Studioと関係がありますか?

これは明らかに言語仕様の範囲内です。ptrの呼び出し後は無効になりますdeleteptrそれがdeleted になった後に使用すると、未定義の動作の原因になります。しないでください。ランタイム環境はptr、を呼び出した後、自由に何でも実行できますdelete

削除がそれが指しているアドレスを変更できるとしたら、削除によって、ポインタがランダムなアドレスの代わりにNULLに自動的に設定されないのはなぜですか?

ポインターの値を古い値に変更することは、言語仕様の範囲内です。それをNULLに変更する限り、それは悪いことだと私は言うでしょう。ポインターの値がNULLに設定されている場合、プログラムはより健全な方法で動作します。しかし、それは問題を隠します。プログラムが別の最適化設定でコンパイルされているか、別の環境に移植されている場合、問題は最も不都合な瞬間に現れる可能性があります。


1
私はそれがOPの質問に答えるとは思わない。
SergeyA

編集後も同意しない。これをNULLに設定しても問題は隠されません-実際には、それがない場合よりも多くのケースで問題が明らかになります。通常の実装がこれを行わない理由があり、その理由は異なります。
SergeyA、2015年

4
@SergeyA、ほとんどの実装は効率化のためにそれを行いません。ただし、実装で設定する場合は、NULL以外の値に設定することをお勧めします。NULLに設定されている場合よりも早く問題が明らかになります。NULLに設定されているためdelete、ポインターを2回呼び出しても問題は発生しません。それは間違いなく良くありません。
R Sahu、2015年

いいえ、効率ではありません-少なくとも、それは主要な問題ではありません。
SergeyA 2015年

7
@SergeyA NULLプロセスのアドレス空間の外ではないが確実にその外の値へのポインタを設定すると、2つの選択肢よりも多くのケースが公開されます。ぶら下がったままにしておいても、解放後に使用した場合、必ずしもsegfaultが発生するわけではありません。再びdに設定してNULLも、segfaultが発生しないように設定deleteします。
2015

10
delete ptr;
cout << "ptr = " << ptr << endl;

一般に、無効なポインターの値(上記のように、これは逆参照とは異なります)を読み取ること(ポインターが無効になると、ポインターが無効になるなどdelete)は、実装で定義された動作です。これはCWG#1438で導入されました。こちらもご覧ください

以前は、無効なポインタの値を読み取ることは未定義の動作でした。そのため、上記の動作は未定義の動作であり、つまり、何かが発生する可能性があることに注意してください。


3
また、[basic.stc.dynamic.deallocation]「標準ライブラリの割り当て解除関数に与えられた引数がnullポインタ値ではないポインタである場合、割り当て解除関数は、ポインタが参照するストレージの割り当てを解除し、すべてを参照するすべてのポインタを無効にします。割り当て解除されたストレージの一部」および[conv.lval](lvalue-> rvalue変換)無効なポインター値を読み取る(セクション4.1)のルールは、実装定義の動作です。
Ben Voigt、2015年

UBでも、特定のベンダーが特定の方法で実装できるため、少なくともそのコンパイラーにとっては信頼性があります。マイクロソフトがCWG#1438より前にポインターのサニタイズ機能を実装することを決定した場合、それはその機能を多かれ少なかれ信頼性のあるものにすることはなかったでしょう。 、標準が言うことに関係なく。
カイルストランド

@KyleStrand:私は基本的にUBの定義を与えました(blog.regehr.org/archives/213)。
giorgim、2015年

1
SOのほとんどのC ++コミュニティーにとって、「何かが起こる可能性がある」ことは完全に文字どおりに解釈れすぎています。これはばかげていると思います。私はUBの定義を理解していますが、コンパイラーは実在の人々によって実装されるソフトウェアの一部にすぎないことも理解しています。それらの人々がコンパイラーを実装して特定の動作をするようにすると、標準の内容に関係なく、コンパイラーはそのように動作します。 。
カイルストランド

1

私はあなたが何らかのデバッグモードを実行していて、VSがポインタを既知の場所に再ポイントしようとしているので、それを逆参照しようとするとさらに追跡および報告できると思います。同じプログラムをリリースモードでコンパイル/実行してみてください。

delete効率を上げるため、および安全性について誤った考えを与えないために、ポインターは通常、内部で変更されません。削除されるポインターは、この場所を指す複数のポインターの1つにすぎない可能性が高いため、削除ポインターを事前定義された値に設定しても、ほとんどの複雑なシナリオでは役に立ちません。

実は、いつものように考えれば考えるほど、VSのせいだと思うようになります。ポインターがconstの場合はどうなりますか?それはまだそれを変えるつもりですか?


ええ、定数のポインタでさえ、この神秘的な8123にリダイレクトされます!
tjwrona1992、2015年

VSには別の石があります:)ちょうど今朝、誰かがVSの代わりにg ++を使用すべき理由を誰かが尋ねました。いきます
SergeyA

7
@SergeyAですが、反対側から、その削除されたポインターをデフェリングすると、segfaultによって、削除されたポインターを参照解除しようしたことが示され、NULLにはなりません。それ以外の場合は、ページが解放された場合にのみクラッシュします(これは非常にまれです)。より速く失敗する; より早く解決します。
ラチェットフリーク

1
@ratchetfreak「速く失敗して、もっと早く解決する」は非常に貴重なマントラですが、「重要な法医学的証拠を破壊することによって失敗する」はそのような価値あるマントラを開始しません。簡単な場合は便利かもしれませんが、より複雑な場合(私たちが最も助けを必要とする傾向がある場合)、貴重な情報を消去すると、問題を解決するために使用できるツールが減少します。
Cort Ammon、2015年

2
@ tjwrona1992:私の考えでは、マイクロソフトはここで正しいことをしています。1つのポインターをサニタイズすることは、何もしないよりはましです。これによりデバッグで問題が発生する場合は、不正な削除呼び出しの前にブレークポイントを配置します。おそらく、このようなものがなければ、問題を見つけることはできません。そして、これらのバグを特定するためのより良いソリューションがある場合は、それを使用して、マイクロソフトが何をするのかをなぜ気にするのですか?
Zan Lynx

0

ポインタを削除した後、それが指しているメモリはまだ有効である可能性があります。このエラーを明示するために、ポインター値は明白な値に設定されます。これは本当にデバッグプロセスに役立ちます。値がに設定されてNULLいる場合、プログラムフローの潜在的なバグとして表示されることはありません。そのため、後でに対してテストするときにバグを隠す可能性がありますNULL

別のポイントは、一部のランタイムオプティマイザがその値をチェックし、その結果を変更する可能性があることです。

以前のバージョンでは、MSは値をに設定していました0xcfffffff

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.