削除する前にNULLポインターを確認する理由はありますか?
-
03-07-2019 - |
質問
ポインターを削除する前に、 NULL
をチェックするレガシーコードをよく目にします。
if (NULL != pSomeObject)
{
delete pSomeObject;
pSomeObject = NULL;
}
削除する前に NULL
ポインターをチェックする理由はありますか?その後ポインターを NULL
に設定する理由は何ですか?
解決
完全に「安全」です。 nullポインターを削除するには;事実上、何もしません。
削除する前にnullを確認する理由は、nullポインターを削除しようとすると、プログラムのバグを示す可能性があるためです。
他のヒント
C ++標準では、 delete-expression (§ 8.5.2.5/2)でNULLポインターを使用することが正当であることを保証しています。ただし、これが割り当て解除関数を呼び出すかどうかは未指定です( operator delete
または operator delete []
;§ 8.5.2.5/ 7、注)。
デフォルトの割り当て解除関数(つまり、標準ライブラリによって提供される)がNULLポインターで呼び出された場合、呼び出しは効果がありません(§ 6.6.4.4.2 / 3)。
ただし、割り当て解除機能が標準ライブラリ—  i.eによって提供されていない場合にどうなるかは不明です。 operator delete
(または operator delete []
)をオーバーロードするとどうなります。
有能なプログラマーは、OP’のコードに示されているように、呼び出し前ではなく、割り当て解除関数の内部に応じてNULLポインターを処理します。同様に、ポインターを nullptr
/ NULL
削除後の目的は非常に限られています。一部の人々は、ディフェンシブプログラミングの精神でこれを行うことを好みます。バグの場合に予測可能:削除後にポインターにアクセスすると、ランダムなメモリー位置へのアクセスではなく、nullポインターアクセスが発生します。どちらの操作も未定義の動作ですが、nullポインターアクセスの動作は実際にははるかに予測可能です(ほとんどの場合、メモリ破損ではなく直接クラッシュが発生します)。メモリ破損はデバッグが特に難しいため、削除されたポインターをリセットするとデバッグに役立ちます。
—もちろん、これは原因ではなく症状(つまり、バグ)を処理しています。 リセットポインターをコードの匂いとして扱う必要があります。クリーンで最新のC ++コードは、メモリの所有権を明確にし、(スマートポインターまたは同等のメカニズムを使用して)静的にチェックするため、この状況を確実に回避できます。
ボーナス:オーバーロードされた operator delete
の説明:
operator delete
は(その名前にもかかわらず)他の関数と同様にオーバーロードされる可能性がある関数です。この関数は、引数が一致する operator delete
の呼び出しごとに内部的に呼び出されます。同じことが operator new
にも当てはまります。
operator new
(および operator delete
)のオーバーロードは、メモリの割り当て方法を正確に制御したい場合に意味があります。これを行うことはそれほど難しくありませんが、正しい動作を保証するためにいくつかの予防措置を講じる必要があります。 Scott Meyersがこれについて詳しく説明しています効果的なC ++ 。
とりあえず、デバッグのために operator new
のグローバルバージョンをオーバーロードしたいとしましょう。これを行う前に、次のコードで何が起こるかについての短い通知があります:
klass* pobj = new klass;
// … use pobj.
delete pobj;
実際にここで何が起こりますか?さて、上記は大まかに次のコードに変換できます:
// 1st step: allocate memory
klass* pobj = static_cast<klass*>(operator new(sizeof(klass)));
// 2nd step: construct object in that memory, using placement new:
new (pobj) klass();
// … use pobj.
// 3rd step: call destructor on pobj:
pobj->~klass();
// 4th step: free memory
operator delete(pobj);
わずかに奇妙な構文で new
を呼び出すステップ2に注意してください。これは、いわゆる placement new
の呼び出しであり、アドレスを取得してそのアドレスにオブジェクトを構築します。この演算子もオーバーロードできます。この場合、クラス klass
のコンストラクターを呼び出すだけです。
今、これ以上苦労せずに、演算子のオーバーロードバージョンのコードを次に示します。
void* operator new(size_t size) {
// See Effective C++, Item 8 for an explanation.
if (size == 0)
size = 1;
cerr << "Allocating " << size << " bytes of memory:";
while (true) {
void* ret = custom_malloc(size);
if (ret != 0) {
cerr << " @ " << ret << endl;
return ret;
}
// Retrieve and call new handler, if available.
new_handler handler = set_new_handler(0);
set_new_handler(handler);
if (handler == 0)
throw bad_alloc();
else
(*handler)();
}
}
void operator delete(void* p) {
cerr << "Freeing pointer @ " << p << "." << endl;
custom_free(p);
}
このコードは、ほとんどの実装と同様に、内部で malloc
/ free
のカスタム実装を使用するだけです。また、デバッグ出力も作成します。次のコードを検討してください:
int main() {
int* pi = new int(42);
cout << *pi << endl;
delete pi;
}
次の出力が生成されました。
Allocating 4 bytes of memory: @ 0x100160
42
Freeing pointer @ 0x100160.
今、このコードは operator delete
の標準実装とは根本的に異なることを行います: nullポインターをテストしませんでした!上記のコードはコンパイルされます
削除は内部的にNULLをチェックします。テストは冗長です
nullの削除は何もしません。 deleteを呼び出す前にnullをチェックする理由はありません。
nullのポインターに関心のある追加情報がある場合は、他の理由でnullをチェックすることをお勧めします。
C ++ 03 5.3.5 / 2によると、nullポインターを削除しても安全です。 これは標準から引用されています:
どちらの場合でも、deleteのオペランドの値が ヌルポインターは操作に影響しません。
pSomeObjectがNULLの場合、deleteは何もしません。いいえ、NULLをチェックする必要はありません。
一部のナックルヘッドがポインターを使用しようとする可能性がある場合は、ポインターを削除した後にNULLを割り当てることをお勧めします。 NULLポインターの使用は、誰が何を知っているかを指すポインターを使用するよりもわずかに優れています(NULLポインターはクラッシュを引き起こしますが、削除されたメモリーへのポインターはそうではないかもしれません)
削除する前にNULLをチェックする理由はありません。 コードのどこかでNULLチェックを実行することでオブジェクトが既に割り当てられているかどうかを確認する場合、削除後にNULLを割り当てる必要があります。例としては、オンデマンドで割り当てられる何らかのキャッシュデータがあります。キャッシュオブジェクトをクリアするたびに、ポインターにNULLを割り当てて、オブジェクトを割り当てるコードが割り当てを実行する必要があることを認識します。
以前の開発者が「冗長に」コーディングしたと思います。数ミリ秒節約するには: 削除時にポインターをNULLに設定するのは良いことです。そのため、オブジェクトを削除した直後に次のような行を使用できます。
if(pSomeObject1!=NULL) pSomeObject1=NULL;
しかし、とにかくdeleteはその正確な比較を行っています(NULLの場合は何もしません)。なぜこれを2回行うのですか? deleteを呼び出した後は、現在の値に関係なく、pSomeObjectをいつでもNULLに割り当てることができますが、その値が既にある場合、これは少し冗長になります。
だから私の賭けは、潜在的に不必要なテストと割り当てのコストをかけることなく、削除後にpSomeObject1が常にNULLになるようにしようとした行の作成者です。
それはあなたが何をしているかに依存します。たとえば、一部の古い free
の実装は、 NULL
ポインターが渡された場合に満足しません。一部のライブラリにはまだこの問題があります。たとえば、Xlibライブラリの XFree
には次のように書かれています。
説明
XFree関数は 汎用Xlibルーチン 指定されたデータを解放します。絶対です 使用していたオブジェクトを解放します Xlibによって割り当てられますが、代替 関数は オブジェクト。 NULLポインターは この関数に渡されます。
したがって、 NULL
ポインターをバグとして解放すると、安全になります。
私の観察では、deleteを使用してnullポインターを削除することは、PARISCやitaniumなどのUNIXベースのマシンで安全です。しかし、プロセスがクラッシュするとLinuxシステムにとっては非常に安全ではありません。