NULL
ポインタを削除する前にレガシーコードがチェックするのをよく見かけます。
if (NULL != pSomeObject)
{
delete pSomeObject;
pSomeObject = NULL;
}
NULL
ポインタを削除する前にポインタをチェックする理由はありますか?NULL
後でポインタをに設定する理由は何ですか?
NULL
ポインタを削除する前にレガシーコードがチェックするのをよく見かけます。
if (NULL != pSomeObject)
{
delete pSomeObject;
pSomeObject = NULL;
}
NULL
ポインタを削除する前にポインタをチェックする理由はありますか?NULL
後でポインタをに設定する理由は何ですか?
null ポインターを削除しても完全に「安全」です。それは事実上ノーオペレーションに相当します。
削除する前に null をチェックする理由は、null ポインターを削除しようとすると、プログラムのバグが示される可能性があるためです。
編集
注:削除演算子をオーバーロードすると、「安全」でなくなる可能性がありますdelete NULL
C++ 標準では、 delete 式でヌル ポインターを使用することが正当であることが保証されています(§8.5.2.5/2)。ただし、これが解放関数を呼び出すかどうかは指定operator delete
されていません (またはoperator delete[]
; §8.5.2.5/7、注)。
デフォルトの割り当て解除関数 (つまり、標準ライブラリによって提供される) が null ポインターで呼び出された場合、その呼び出しは効果がありません (§6.6.4.4.2/3)。
しかし、解放関数が標準ライブラリによって提供されていない場合に何が起こるかは不明です — つまり、operator delete
(またはoperator delete[]
) をオーバーロードしたときに何が起こるかは不明です。
有能なプログラマーは、OP のコードに示されているように、呼び出しの前ではなく、割り当て解除関数内nullptr
でそれに応じて null ポインターを処理しますNULL
。防御的プログラミングの精神でこれを行うのが好きな人もいます: バグが発生した場合のプログラムの動作が少し予測しやすくなります: 削除後にポインタにアクセスすると、ランダムなメモリ位置へのアクセスではなく、ヌル ポインタ アクセスが発生します。どちらの操作も未定義の動作ですが、null ポインター アクセスの動作は、実際にははるかに予測可能です (ほとんどの場合、メモリの破損ではなく、直接的なクラッシュが発生します)。メモリの破損はデバッグが特に難しいため、削除されたポインタをリセットするとデバッグに役立ちます。
— もちろん、これは原因 (すなわちバグ) ではなく症状を治療しています。ポインターのリセットは、コードの匂いとして扱う必要があります。クリーンで最新の C++ コードでは、メモリの所有権が明確になり、(スマート ポインターまたは同等のメカニズムを使用して) 静的にチェックされるため、この状況を確実に回避できます。
operator delete
:operator delete
は (その名前にもかかわらず) 他の関数と同様にオーバーロードできる関数です。operator delete
この関数は、引数が一致するの呼び出しごとに内部的に呼び出されます。についても同様ですoperator new
。
メモリの割り当て方法を正確に制御したい場合には、オーバーロードoperator new
(および も) が理にかなっています。operator delete
これを行うのはそれほど難しいことではありませんが、正しい動作を保証するためにいくつかの予防措置を講じる必要があります。Scott Meyers はこれについて、Effective 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 に注意してください。これは、アドレスを取得してそのアドレスにオブジェクトを構築する、いわゆる配置の呼び出しです。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 ポインタをテストしていません! コンパイラはこれをチェックしないので、上記のコードはコンパイルされますが、ヌル ポインタを削除しようとすると、実行時に厄介なエラーが発生する可能性があります。
ただし、前に述べたように、この動作は実際には予期しないものであり、ライブラリの作成者はoperator delete
. このバージョンは大幅に改善されています:
void operator delete(void* p) {
if (p == 0) return;
cerr << "Freeing pointer @ " << p << "." << endl;
free(p);
}
結論として、ずさんな実装でoperator delete
はクライアント コードで明示的な null チェックが必要になる場合がありますが、これは非標準の動作であり、従来のサポートでのみ許容されるべきです (許容される場合)。
null の削除はノーオペレーションです。delete を呼び出す前に null をチェックする必要はありません。
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
一部のライブラリには、まだこの問題があります。たとえばXFree
、Xlib ライブラリには次のように書かれています。
説明
XFree 関数は、指定されたデータを解放する汎用 Xlib ルーチンです。オブジェクトに対して代替関数が明示的に指定されていない限り、Xlib によって割り当てられたすべてのオブジェクトを解放するために、これを使用する必要があります。この関数に NULL ポインターを渡すことはできません。
したがって、ポインターをバグとして解放NULL
することを検討してください。そうすれば安全です。
私の観察によると、delete を使用してヌル ポインターを削除することは、PARISC や itanium などの UNIX ベースのマシンでは安全です。ただし、プロセスがクラッシュするため、Linux システムにとっては非常に安全ではありません。