ポインタを削除した後に NULL にすることは良い習慣ですか?
-
20-09-2019 - |
質問
まず言っておきますが、 スマート ポインターを使用すれば、このことを心配する必要はありません。
次のコードにはどのような問題があるのでしょうか?
Foo * p = new Foo;
// (use p)
delete p;
p = NULL;
これがきっかけとなったのは、 答えとコメント 別の質問に。からの 1 つのコメント ニール・バターワース いくつかの賛成票が生成されました:
削除後にポインターを NULL に設定することは、C++ では一般的な良い習慣ではありません。それが良いことである場合もあれば、無意味でエラーを隠すことができる場合もあります。
それが役に立たない状況はたくさんあります。しかし、私の経験では、それは害にはなりません。誰か私を啓発してください。
解決
0へのポインタを設定する(NULLは、Cから定義、標準C ++の「NULL」であることは多少異なる)二重削除上のクラッシュを回避します。
次の点を考慮します:
Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything
これに対します:
Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior
あなたが0に削除されたポインタを設定しない場合は、二重の削除をやっている場合は、つまり、あなたはトラブルになります。削除後の0へのポインタを設定に対する引数がそうすることが、単にマスクはダブルそれらを未処理のバグを削除し、去ることになります。
これは明らかに、二重の削除バグを持っていないのがベストですが、所有権の意味とオブジェクトのライフサイクルに応じて、これは実際に達成するのは難しいことができます。私はUBの上にマスクされた二重の削除バグを好むます。
最後に、オブジェクトの割り当てを管理に関する追記、私はあなたのニーズに応じて、共有所有権に対する厳しい/単数所有権、std::unique_ptr
、または別のスマートポインタの実装のためのstd::shared_ptr
を見てみましょうお勧めします。
他のヒント
ポインタが指すものを削除した後でポインタを NULL に設定しても問題はありませんが、多くの場合、より根本的な問題に対するちょっとした応急処置になります。そもそもなぜポインタを使うのでしょうか?典型的な理由が 2 つあります。
- 単にヒープに何かを割り当てたかっただけです。この場合、RAII オブジェクトでラップする方がはるかに安全でクリーンになります。オブジェクトが必要なくなったら、RAII オブジェクトのスコープを終了します。そのようにして
std::vector
は機能し、割り当て解除されたメモリへのポインタが誤って周囲に残る問題も解決されます。ポインタはありません。 - あるいは、複雑な共有所有権のセマンティクスが必要な場合もあります。から返されたポインタ
new
それと同じではないかもしれませんdelete
が呼び出されます。その間、複数のオブジェクトがそのオブジェクトを同時に使用した可能性があります。その場合、共有ポインタなどの方が望ましいでしょう。
私の経験則では、ユーザー コード内にポインタを残したままにすると、それは間違ったことになります。そもそもポインタはゴミを指すためにあるべきではありません。なぜオブジェクトがその有効性を保証する責任を負わないのでしょうか?ポイントされたオブジェクトが終了するときに、そのスコープが終了しないのはなぜですか?
私はより良いベストプラクティスを持っています!
{
Foo* pFoo = new Foo;
// use pFoo
delete pFoo;
}
私は常にポインタを次のように設定します NULL
(今 nullptr
) それが指すオブジェクトを削除した後。
これは、解放されたメモリへの多くの参照を捕捉するのに役立ちます (ヌル ポインターの参照解除でプラットフォームに障害が発生すると仮定します)。
たとえば、ポインタのコピーが存在する場合、解放されたメモリへのすべての参照は捕捉されません。しかし、何もないよりはいくつかあったほうが良いでしょう。
二重削除はマスクされますが、既に解放されたメモリへのアクセスよりもはるかに頻度が低いことがわかります。
多くの場合、コンパイラーはそれを最適化して取り除きます。したがって、不要だという議論は私には説得力がありません。
すでに RAII を使用している場合は、それほど多くはありません。
delete
s はそもそもコードに含まれているため、追加の代入が乱雑さを引き起こすという議論は私には説得力がありません。デバッグする場合、古いポインターではなく null 値を確認すると便利なことがよくあります。
それでも気になる場合は、代わりにスマート ポインターまたはリファレンスを使用してください。
また、リソースが解放されるときに、他のタイプのリソース ハンドルを no-resource 値に設定します (これは通常、リソースをカプセル化するために作成された RAII ラッパーのデストラクター内でのみ行われます)。
私は大規模な (900 万ステートメント) 商用製品 (主に C 言語) に取り組みました。ある時点で、メモリが解放されるときにマクロ マジックを使用してポインタを無効にしていました。これにより、多くの潜在的なバグが直ちに明らかになり、すぐに修正されました。私が覚えている限り、ダブルフリーのバグは一度もありませんでした。
アップデート: Microsoft は、これがセキュリティにとって良い実践であると信じており、SDL ポリシーでこの実践を推奨しています。どうやら MSVC++11 は 削除されたポインタを踏みつける /SDL オプションを使用してコンパイルすると、(多くの状況で) 自動的に実行されます。
まず、この上の既存の質問と密接に関連するトピックの多くは、例えば、ある<のhref = "https://stackoverflow.com/questions/704466/why-doesnt-delete-set-the-pointer- 「ヌル-へ>なぜNULLにポインタを設定して削除されませんですかます。
あなたのコードでは、(使用P)で何が起こっ問題。たとえば、あなたがこのようなコードを持ってどこかにいる場合:
Foo * p2 = p;
あなたはまだ心配するポインタP2を持っているように、その後、NULLにPを設定すると、非常に少ない実現します。
これは、NULLへのポインタを設定すると、常に無意味であると言っているわけではありません。 pは寿命が正確にPを含むクラスと同じではなかったのリソースへのメンバ変数の指示した場合、例えば、NULLに次に設定pはリソースの有無を示すのに有用な方法である可能性があります。
delete
後に多くのコード、はいがある場合。ポインタがコンストラクタまたはメソッドまたは関数の終了時に削除されたときに、番号
このたとえ話のポイントは、オブジェクトがすでに削除されていることを、実行時に、プログラマを思い出させるためである。
より良い練習が自動的に彼らのターゲットオブジェクトを削除するスマートポインタ(共有またはスコープ)を使用することです。
他の人が言ったように、delete ptr; ptr = 0;
は悪魔があなたの鼻の外に飛ぶさせるつもりはありません。しかし、それは一種のフラグとしてptr
の利用を奨励しません。コードはdelete
だらけとNULL
へのポインタを設定することになります。次のステップは、if (arg == NULL) return;
ポインタの偶然の使い方から保護するために、あなたのコードをNULL
を散乱することです。 NULL
に対するチェックがオブジェクトまたはプログラムの状態をチェックするあなたの主な手段となったら、問題が発生します。
私はどこかにフラグとしてポインタを使用する方法についてコードのにおいがあることを確信しているが、私は1つを発見していない。
質問を少し変更します。
初期化されたポインターを使用しますか?ご存知のように、指し示すメモリをnullしたり割り当てたりすることはできませんでしたか?
ポインターの NULL への設定をスキップできるシナリオは 2 つあります。
- ポインタ変数はすぐにスコープ外になります
- ポインターのセマンティクスをオーバーロードし、その値をメモリー ポインターとしてだけでなく、キーまたは生の値としても使用しています。ただし、このアプローチには別の問題があります。
一方、ポインタを NULL に設定するとエラーが隠れる可能性があるという主張は、修正によって別のバグが隠れてしまう可能性があるため、バグを修正すべきではない、と主張しているように私には聞こえます。ポインターが NULL に設定されていない場合に発生する可能性のある唯一のバグは、ポインターを使用しようとするバグです。しかし、これを NULL に設定すると、解放されたメモリで使用した場合とまったく同じバグが実際に発生します。
(1つのそのような制約は、<のhref = "https://stackoverflow.com/questions/1931126/で言及されました/ 1931149#1931149" それは、グッド・プラクティス・ツー・ヌル・ポインタ-後削除-ことである>ニールバターワースの)、その後、私の個人的な好みがあること、それを残すことです。
は、私にとっては、問題ではない「これは良いアイデアです?」しかし、「どのような行動は、私が防止またはこれを行うことによって、成功することができますか?」これは、他のコードは、ポインタが利用できなくなったことを見ていないことを可能にする場合たとえば、なぜ他のコードも、彼らが解放された後に解放されたポインタを見しようとしていますか?通常、それはバグです。
また、必要以上の仕事だけでなく、事後のデバッグを妨害しません。少ないあなたがそれを必要としないの後にあなたがメモリに触れ、容易にそれは何かがクラッシュした理由を把握することです。何度も私はメモリは、特定のバグが診断するのに発生し、修正が言ったときと同様の状態にあるという事実に頼ってきたバグます。
は明示的に強く、削除後にゼロにすることはポインタが概念的に何かオプションを表し読者に示唆しています。私が行われているのを見た場合、私はどこでもソースでポインタが、それは最初のNULLに対してテストする必要があることに使用されることを心配し始めると思います。
それはあなたが実際に何を意味するかだ場合は、は、<のhref = "http://www.boost.org/doc/libs/1_37_0/libs/optional/doc/htmlのようなものを使用してソースにそれが明示的にする方が良いでしょう/index.html#optional.motivation」のrel = "nofollowをnoreferrer">ブースト::オプションの
optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();
しかし、あなたが本当にポインタを知る人は「悪いなくなって」いたい場合は、私が行うための最善のことは、それがスコープ外に行くようであると言う人たちと100%一致してピッチます。その後、実行時に悪いデリファレンスの可能性を防ぐために、コンパイラを使用しています。
これは、すべてのC ++風呂で赤ちゃん、それを捨てるべきではありません。 :)
、適切なエラーチェックとよく構造化されたプログラムでは、ヌルを割り当てるする理由のないのではありません。 0
は、この文脈では普遍的に認められた無効な値として単独で立っています。すぐに硬く、フェイルます。
0
を割り当てるに対する引数の多くは、それがの可能性のバグを非表示にしたり、制御フローを複雑にすることを示唆しています。基本的に、それは上流のエラー()悪いしゃれのためのあなたのせい(申し訳ありませんではない)やプログラマに代わって別のエラーのいずれかである - 。プログラムの流れが複雑すぎる成長してきたことをおそらく表示
、それは彼らが意図的に導入している合併症です。検疫良く、早くあなたは誤用の例を見つけ、そしてより少ない彼らは他のプログラムに拡散することができます。
さて構造化プログラムは、これらのケースを避けるために、C ++の機能を使用して設計することができます。あなたは、参照を使用することができ、またはあなただけの「NULLまたは無効な引数を使用して/渡すとエラーになります」と言うことができる - などスマートポインタなどの容器にも同様に適用可能であるアプローチ。一貫して正しい行動を増加させると、これまで得ることから、これらのバグを禁じます。
そこから、ヌルポインタが存在してもよい(または許可された)非常に限られた範囲およびコンテキストを持っています。
同じことがconst
ないポインタに適用することができます。その範囲は非常に小さく、不適切な使用がチェックされ、明確に定義されているため、ポインタの値に従うことは簡単です。あなたのツールセットとエンジニアが迅速に読み取り、次のプログラムに従うことができない、または不適切なエラーチェックや一貫性のない/寛大なプログラムの流れがある場合は、他の、より大きな問題を抱えてます。
最後に、あなたのコンパイラおよび環境はおそらく、解放されたメモリへのアクセスを検出し、およびその他の関連UBをキャッチ倍のためのいくつかの警備員を持っています。あなたはまた、既存のプログラムに影響を与えることなく、多くの場合、あなたのプログラムに同様の診断を導入することができます。
すでに質問に記載されている内容を拡張させてください。
質問に入力した内容を箇条書きで示します。
削除後にポインターを NULL に設定することは、C++ では一般的な良い習慣ではありません。次のような場合があります。
- それは良いことです
- また、無意味でエラーが隠れてしまう場合もあります。
ただし、 何度もありません これがいつであるか 悪い!あなたはするであろう ない 明示的に null にすることでさらなるバグが発生することはありません。 リーク 記憶、あなたはしないだろう 未定義の動作を引き起こす 発生する。
したがって、疑わしい場合は、それを無効にしてください。
そうは言っても、何らかのポインターを明示的に null にする必要があると感じる場合は、メソッドを十分に分割していないように思えます。メソッドを分割する「メソッド抽出」と呼ばれるリファクタリング アプローチを検討する必要があります。別々の部品。
はいます。
それを行うことができる唯一の「害」は、あなたのプログラムに非効率性(不要な店舗運営を)導入することである - しかし、このオーバーヘッドはほとんどの場合、メモリのブロックを割り当て、解放のコストに関連して無視できるほど小さくなります。 P> あなたがそれをしない場合は、
は、<全角> の1日のいくつかの厄介なポインタderefernceのバグを持っています。
私はいつも、削除のためのマクロを使用します:
#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }
(およびアレイのための同様の、フリー()、ハンドルを解放する)
また、呼び出し元のコードのポインタへの参照を取る方法を「自己削除」書くことができますので、彼らはNULLに呼び出し元のコードのポインタを強制します。例えば、多くのオブジェクトのサブツリーを削除します:
static void TreeItem::DeleteSubtree(TreeItem *&rootObject)
{
if (rootObject == NULL)
return;
rootObject->UnlinkFromParent();
for (int i = 0; i < numChildren)
DeleteSubtree(rootObject->child[i]);
delete rootObject;
rootObject = NULL;
}
編集
はい、これらの技術は、マクロの使用に関するいくつかのルールに違反しない(はい、これらの日は、あなたはおそらくテンプレートと同じ結果を得ることができ) - しかし、長年使用して、私は決しての死者アクセスメモリ - あなたが直面することができ、デバッグの問題にかかり厄介かつ最も困難かつ最も時間の一つ。長年にわたり実際には、彼らは効果的に、私はそれらを導入しているすべてのチームからのバグのwhjoleクラスを排除してきました。
あなたが実装できる多くの方法があります上記の - 私はちょうど彼らはむしろ彼らがNULLのないメモリを解放するための手段を提供するよりも、オブジェクトを削除する場合は、ポインタをNULLに人々を強制するという考えを説明しようとしています呼び出し側のポインタます。
もちろん、上記の例では、自動ポインタに向かってわずかなステップです。どのOPは、特に自動車のポインタを使用しない場合について尋ねられたので、私はお勧めしませんでした。
「それは無意味であるとエラーを非表示にすることができます場合があり、行うには良いことである場合があり、そして」
私は2つの問題を見ることができます: そのシンプルコード:
delete myObj;
myobj = 0
は、マルチスレッド環境でのために、ライナーになります:
lock(myObjMutex);
delete myObj;
myobj = 0
unlock(myObjMutex);
ドン・ノイフェルドの「ベストプラクティス」は常に適用されません。例えば。 1つの自動車のプロジェクトで私たちも、デストラクタで0へのポインタを設定する必要がありました。私は、このようなルールは珍しくありませんセーフティクリティカルなソフトウェアに想像することができます。説得しようとするよりも、それらに従うことを簡単に(と賢明)であります コード内の各ポインタの使用のためのチーム/コードチェッカー、このポインタをゼロ線が冗長であること。
もう一つの危険性は、例外-使用してコードでは、この技術に頼っています
try{
delete myObj; //exception in destructor
myObj=0
}
catch
{
//myObj=0; <- possibly resource-leak
}
if (myObj)
// use myObj <--undefined behaviour
このようなコードでは、リソース・リークを生成し、問題やプロセスがクラッシュを延期する。
のいずれかだから、私の頭を自然に起こって、この二つの問題は、「どのようにスマートポインタを使用しないように、通常のポインタを安全に仕事をする」私のための種類のすべての質問を作る(ハーブサッターは確かに多くを言うだろうため)時代遅れのようにます。
は常にあるます。
ポインターを再度使用する前にポインターを再割り当てする場合 (逆参照、関数に渡すなど)、ポインターを NULL にするのは単なる追加の操作です。ただし、再度使用する前に再割り当てされるかどうかが不明な場合は、NULL に設定することをお勧めします。
多くの人が言っているように、もちろん、スマート ポインターを使用する方がはるかに簡単です。
編集:トーマス・マシューズが次のように述べています。 この前の回答, 、ポインタがデストラクタ内で削除された場合、オブジェクトはすでに破棄されているため、ポインタは再度使用されないため、ポインタに NULL を割り当てる必要はありません。
Iは、単一の機能(またはオブジェクト)でそれを再利用する正当のシナリオがあるまれに有用であることを削除した後NULLへのポインタを設定する想像できます。そうでなければ、それは意味がありません - ポインタは、それが存在する意味のある何かを指すように必要 - 。ピリオドを
のコードは、アプリケーションの最もパフォーマンスが重要な部分に属してそれをシンプルに保つとのshared_ptrを使用しない場合:
shared_ptr<Foo> p(new Foo);
//No more need to call delete
これは参照カウントを実行し、スレッドセーフです。あなたは(、の#include <メモリ>はstd :: tr1を名前空間)TR1でそれを見つけるか、またはあなたのコンパイラがそれを提供しない場合、ブーストからそれを得ることができます。