165

まず、スマート ポインターを使用すれば、これについて心配する必要はありません。

次のコードの問題点は何ですか?

Foo * p = new Foo;
// (use p)
delete p;
p = NULL;

これは、別の質問への回答とコメントによって引き起こされました。Neil Butterworthからの 1 つのコメントは、いくつかの賛成票を生成しました。

削除後にポインタを NULL に設定することは、C++ では一般的な適切な方法ではありません。それが良いことである場合もあれば、無意味でエラーを隠すことができる場合もあります。

それが役に立たない状況はたくさんあります。しかし、私の経験では、それは害になることはありません。誰かが私を啓発します。

4

18 に答える 18

103

ポインターを 0 (標準 C++ では「null」であり、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 

つまり、deleted ポインターを 0 に設定しないと、二重削除を行うと問題が発生します。削除後にポインタを 0 に設定することに対する議論は、そうすると二重削除のバグがマスクされ、未処理のままになるというものです。

明らかに、二重削除バグが発生しないことが最善ですが、所有権のセマンティクスとオブジェクトのライフサイクルによっては、これを実際に達成するのは難しい場合があります。私は、UB よりもマスクされた二重削除バグを好みます。

std::unique_ptr最後に、オブジェクトの割り当ての管理に関する補足事項として、必要に応じて、厳密な/単一の所有権、std::shared_ptr共有所有権、または別のスマート ポインターの実装を確認することをお勧めします。

于 2009-12-18T23:01:39.510 に答える
60

ポインターが指すものを削除した後でポインターを NULL に設定しても問題はありませんが、多くの場合、より根本的な問題に対する応急処置のようなものです。そもそもなぜポインターを使用するのでしょうか? 典型的な理由が 2 つあります。

  • ヒープに何かを割り当てたかっただけです。その場合、それを RAII オブジェクトにラップすると、はるかに安全でクリーンになります。オブジェクトが不要になったら、RAII オブジェクトのスコープを終了します。それがどのようstd::vectorに機能するかであり、割り当て解除されたメモリへのポインターを誤って残すという問題を解決します。ポインターはありません。
  • または、複雑な共有所有権のセマンティクスが必要な場合もあります。から返されたポインタは、呼び出されnewたものと同じではない場合があります。deleteその間に、複数のオブジェクトが同時にオブジェクトを使用した可能性があります。その場合、共有ポインタまたは同様のものが望ましいでしょう。

私の経験則では、ユーザー コード内にポインターを残しておくと、間違った操作を行っていることになります。そもそもポインタがガベージを指していてはいけません。有効性を保証する責任を負うオブジェクトが存在しないのはなぜですか? ポイント先のオブジェクトが終了するときに、そのスコープが終了しないのはなぜですか?

于 2009-12-18T22:55:08.423 に答える
45

さらに良いベスト プラクティスがあります。可能であれば、変数のスコープを終了してください。

{
    Foo* pFoo = new Foo;
    // use pFoo
    delete pFoo;
}
于 2009-12-18T22:49:38.210 に答える
33

指すオブジェクトを削除した後、常にポインターをNULL(今nullptr) に設定します。

  1. 解放されたメモリへの多くの参照をキャッチするのに役立ちます (null ポインターの deref でプラットフォームに障害が発生したと仮定します)。

  2. たとえば、ポインターのコピーが横たわっている場合、解放されたメモリへのすべての参照をキャッチすることはできません。しかし、いくつかは、ないよりはましです。

  3. 二重削除はマスクされますが、すでに解放されたメモリへのアクセスよりもはるかに一般的ではありません。

  4. 多くの場合、コンパイラはそれを最適化します。したがって、それが不要であるという議論は私を説得しません。

  5. すでに RAII を使用している場合は、delete最初からコード内に s があまりないため、余分な代入が乱雑さを引き起こすという議論には説得力がありません。

  6. デバッグ時に、古いポインターではなく null 値を確認すると便利なことがよくあります。

  7. それでも気になる場合は、代わりにスマート ポインターまたは参照を使用してください。

また、リソースが解放されたときに、他のタイプのリソース ハンドルを no-resource 値に設定します (これは通常、リソースをカプセル化するために作成された RAII ラッパーのデストラクタにのみ存在します)。

大規模な (900 万ステートメント) 商用製品 (主に C) に取り組みました。ある時点で、マクロ マジックを使用して、メモリが解放されたときにポインターを null にしました。これにより、多くの潜んでいるバグがすぐに明らかになり、すぐに修正されました。私が覚えている限り、ダブルフリーのバグは一度もありませんでした。

更新: Microsoft は、これがセキュリティの優れたプラクティスであると考えており、SDL ポリシーでこのプラクティスを推奨しています。/SDL オプションを指定してコンパイルすると、MSVC++11 は(多くの状況で)削除されたポインターを自動的に踏みにじるようです。

于 2009-12-19T00:24:35.367 に答える
12

まず、これと密接に関連するトピックに関する既存の質問がたくさんあります。たとえば、なぜ削除はポインターを NULL に設定しないのですか? .

あなたのコードでは、何が起こっているのか(pを使用)の問題です。たとえば、どこかに次のようなコードがあるとします。

Foo * p2 = p;

次に、 p2 を心配するポインターがまだあるため、 p を NULL に設定してもほとんど効果がありません。

これは、ポインターを NULL に設定することが常に無意味であると言っているわけではありません。たとえば、p がリソースを指すメンバー変数であり、リソースの有効期間が p を含むクラスと正確に同じではない場合、p を NULL に設定すると、リソースの存在または不在を示す便利な方法になる可能性があります。

于 2009-12-18T22:56:17.503 に答える
7

の後にさらにコードがある場合はdelete、はい。ポインターがコンストラクター内またはメソッドまたは関数の最後で削除された場合、No.

このたとえ話のポイントは、実行時にオブジェクトがすでに削除されていることをプログラマーに思い出させることです。

さらに良い方法は、ターゲット オブジェクトを自動的に削除するスマート ポインター (共有またはスコープ) を使用することです。

于 2009-12-18T22:52:33.887 に答える
3

他の人が言ったようdelete ptr; ptr = 0;に、悪魔が鼻から飛び出すことはありません. ただし、ptrある種のフラグとして を使用することをお勧めします。コードは散らかりdelete、ポインターを に設定しますNULL。次のステップは、ポインターif (arg == NULL) return;の誤った使用から保護するために、コード全体に分散することです。NULLこの問題は、チェックがNULLオブジェクトまたはプログラムの状態をチェックする主要な手段になると発生します。

どこかでポインターをフラグとして使用するコードの匂いがあると確信していますが、見つけられませんでした。

于 2009-12-18T23:21:24.150 に答える
2

削除後に明示的にnullを指定すると、ポインタが概念的にオプションの何かを表すことを読者に強く示唆します。それが行われているのを見たら、ソースのどこでもポインタが使用され、最初にNULLに対してテストする必要があるのではないかと心配し始めます。

それが実際の意味である場合は、boost::optionalなどを使用してソースで明示的にすることをお勧めします。

optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();

しかし、もしあなたが本当にポインターが「悪くなった」ことを人々に知ってもらいたいのなら、私はそれを範囲外にすることが最善のことだと言う人々と100%一致して売り込みます。次に、コンパイラを使用して、実行時に不正な逆参照が発生する可能性を防ぎます。

それはすべてのC++風呂の赤ちゃんです、それを捨てるべきではありません。:)

于 2009-12-19T01:17:08.673 に答える
2

ポインターを削除した後にポインターを NULL に設定するかどうかを強制する他の制約がない場合 (そのような制約の 1 つはNeil Butterworthによって言及されています)、私の個人的な好みはそのままにしておくことです。

私にとって問題は、「これでいいのか?」ということではありません。しかし、「これを行うことで、どのような行動を防止または成功させることができますか?」たとえば、これにより、他のコードがポインターが使用できなくなったことを確認できる場合、他のコードが解放されたポインターを解放後に調べようとするのはなぜでしょうか? 通常、それはバグです。

また、必要以上の作業を行うだけでなく、事後分析のデバッグを妨げます。メモリが不要になった後でメモリに触れることが少なければ少ないほど、何かがクラッシュした理由を簡単に突き止めることができます。私は何度も、特定のバグが発生したときと同様の状態にメモリがあるという事実に基づいて、そのバグを診断して修正しました。

于 2009-12-18T23:26:47.383 に答える
2

適切なエラー チェックを備えた適切に構造化されたプログラムでは、null を割り当てない理由はありません。0このコンテキストでは、広く認識されている無効な値として単独で使用されます。激しく失敗し、すぐに失敗します。

割り当てに反対する議論の多くは、割り当てによってバグが隠されたり、制御フローが複雑になったりする可能性が0あることを示唆しています。基本的に、これは上流のエラー (あなたのせいではありません (悪いしゃれで申し訳ありません)) か、プログラマーに代わって別のエラーが発生したかのいずれかです。

プログラマーが特別な値として null の可能性があるポインターの使用を導入し、その周りに必要な回避策をすべて記述したい場合、それは彼らが意図的に導入した複雑さです。検疫が適切であるほど、誤用のケースをより早く発見し、他のプログラムに広がる可能性が低くなります.

これらのケースを回避するために、適切に構造化されたプログラムを C++ 機能を使用して設計することができます。参照を使用することも、単に「null または無効な引数を渡す/使用することはエラーです」と言うことができます。これは、スマート ポインターなどのコンテナーにも同様に適用できるアプローチです。一貫性のある正しい動作を増やすことで、これらのバグが遠ざかるのを防ぎます。

そこから、null ポインターが存在する可能性がある (または許可される) 非常に限られたスコープとコンテキストしかありません。

ではないポインタについても同様constです。ポインターの値を追跡することは、スコープが非常に小さく、不適切な使用がチェックされ、明確に定義されているため、簡単です。ツールセットとエンジニアが簡単な読み取りに続いてプログラムをたどることができない場合、または不適切なエラー チェックや一貫性のない/寛大なプログラム フローがある場合は、別の大きな問題が発生します。

最後に、コンパイラと環境には、エラー (落書き) を導入したり、解放されたメモリへのアクセスを検出したり、他の関連する UB をキャッチしたりする場合に備えて、いくつかのガードがある可能性があります。多くの場合、既存のプログラムに影響を与えることなく、同様の診断をプログラムに導入することもできます。

于 2012-02-28T07:43:22.367 に答える
2

質問を少し変更します。

初期化されていないポインターを使用しますか? NULL に設定していないか、それが指すメモリを割り当てていませんか?

NULL へのポインターの設定をスキップできるシナリオが 2 つあります。

  • ポインター変数はすぐにスコープ外になります
  • ポインターのセマンティックをオーバーロードし、その値をメモリ ポインターとしてだけでなく、キーまたは生の値としても使用しています。ただし、このアプローチには他の問題があります。

一方、ポインターを NULL に設定するとエラーが隠される可能性があると主張することは、修正によって別のバグが隠される可能性があるため、バグを修正すべきではないと主張するように聞こえます。ポインターが NULL に設定されていない場合に表示される唯一のバグは、ポインターを使用しようとするバグです。しかし、これを NULL に設定すると、解放されたメモリで使用した場合とまったく同じバグが実際に発生しますね。

于 2009-12-18T23:04:50.033 に答える
1

「良いこともあるし、無意味で間違いを隠せる時もある」

私は 2 つの問題を見ることができます: その単純なコード:

delete myObj;
myobj = 0

マルチスレッド環境では for-liner になります:

lock(myObjMutex); 
delete myObj;
myobj = 0
unlock(myObjMutex);

Don Neufeld の「ベスト プラクティス」が常に適用されるとは限りません。たとえば、ある自動車プロジェクトでは、デストラクタでもポインタを 0 に設定する必要がありました。安全性が重要なソフトウェアでは、そのようなルールは珍しくありません。コード内の各ポインターの使用についてチーム/コードチェッカーを説得しようとするよりも、それらに従う方が簡単です (そして賢明です)。このポインターを null にする行は冗長です。

別の危険は、例外を使用するコードでこの手法に依存することです。

try{  
   delete myObj; //exception in destructor
   myObj=0
}
catch
{
   //myObj=0; <- possibly resource-leak
}

if (myObj)
  // use myObj <--undefined behaviour

このようなコードでは、リソース リークを生成して問題を延期するか、プロセスがクラッシュします。

したがって、この 2 つの問題が私の頭の中を自発的に通り過ぎていきます (Herb Sutter はもっと多くのことを教えてくれるはずです)。

于 2014-01-27T11:38:52.940 に答える
1

はい。

それができる唯一の「害」は、プログラムに非効率性 (不要なストア操作) を導入することですが、ほとんどの場合、メモリ ブロックの割り当てと解放のコストに比べれば、このオーバーヘッドは取るに足らないものです。

そうしないと、ある日、厄介なポインター逆参照バグが発生することになります。

削除には常にマクロを使用します。

#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }

(配列の場合も同様で、free()、ハンドルの解放)

呼び出し元コードのポインターへの参照を取得する「自己削除」メソッドを作成して、呼び出し元コードのポインターを強制的に 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;
}

編集

はい、これらの手法はマクロの使用に関するいくつかのルールに違反しています (そして、最近では、おそらくテンプレートを使用して同じ結果を達成することができます)。直面する可能性のある問題をデバッグするのに最も時間がかかります。実際には、長年にわたって、私が導入したすべてのチームから、非常に大きなバグを効果的に排除してきました。

上記を実装する方法もたくさんあります。呼び出し元のポインターを NULL しないメモリを解放する手段を提供するのではなく、オブジェクトを削除した場合にポインターを強制的に NULL にするという考えを説明しようとしています。 .

もちろん、上記の例は自動ポインタへの一歩にすぎません。OPが自動ポインターを使用しない場合について具体的に尋ねていたので、私は提案しませんでした。

于 2009-12-18T23:11:25.010 に答える
1

あなたがすでにあなたの質問に入れたものを拡張させてください。

箇条書き形式で質問に入力した内容は次のとおりです。


削除後にポインタを NULL に設定することは、C++ では一般的な適切な方法ではありません。次のような場合があります。

  • それは良いことです
  • 無意味でエラーを隠すことができる場合もあります。

しかし、これが悪いことはありません!明示的に null にすることでバグが増えることはなく、メモリリークも発生せず、未定義の動作が発生することもありません。

したがって、疑わしい場合は、それを無効にしてください。

そうは言っても、何らかのポインターを明示的に null にする必要があると感じた場合、メソッドを十分に分割していないように思えます。メソッドを分割するために「メソッドの抽出」と呼ばれるリファクタリング アプローチを検討する必要があります。別の部品。

于 2009-12-18T22:57:19.033 に答える
0

心配するダングリングポインターが常にあります。

于 2009-12-18T22:57:42.793 に答える
0

再度使用する前にポインターを再割り当てする場合 (逆参照する、関数に渡すなど)、ポインターを NULL にするのは余分な操作です。ただし、再度使用する前に再割り当てされるかどうかわからない場合は、NULL に設定することをお勧めします。

多くの人が言っているように、もちろん、スマート ポインターを使用する方がはるかに簡単です。

編集:Thomas Matthews がこの以前の回答で述べたように、ポインタがデストラクタで削除された場合、オブジェクトが既に破棄されているため再度使用されないため、ポインタに NULL を割り当てる必要はありません。

于 2009-12-18T23:02:50.067 に答える
0

ポインターを削除した後に NULL に設定すると、単一の関数 (またはオブジェクト) でポインターを再利用する正当なシナリオがあるまれなケースで役立つことが想像できます。そうでなければ意味がありません - ポインタは存在する限り意味のあるものを指す必要があります - ピリオド。

于 2009-12-19T17:14:33.860 に答える
0

コードがアプリケーションの最もパフォーマンスが重要な部分に属していない場合は、コードをシンプルに保ち、shared_ptr を使用します。

shared_ptr<Foo> p(new Foo);
//No more need to call delete

参照カウントを実行し、スレッドセーフです。tr1 (std::tr1 名前空間、#include < メモリ >) で見つけることができます。または、コンパイラで提供されていない場合は、boost から取得します。

于 2009-12-19T19:10:43.907 に答える