2 番目のほうがはるかに重要です。解放されたポインターを再利用すると、微妙なエラーになる可能性があります。再利用されたポインターがたまたま指しているメモリに、一見無関係なコードが書き込まれたため、コードは正常に動作し続け、明確な理由もなくクラッシュします。
私はかつて、他の誰かが書いた本当にバグのあるプログラムに取り組まなければなりませんでした。私の本能によると、多くのバグは、メモリを解放した後もポインタを使い続けようとするずさんな試みに関連しています。メモリを解放した後にポインターを NULL に設定するようにコードを変更したところ、bamでヌル ポインター例外が発生し始めました。すべての null ポインター例外を修正した後、突然、コードがはるかに安定しました。
私自身のコードでは、free() のラッパーである独自の関数のみを呼び出します。ポインターへのポインターを取り、メモリを解放した後にポインターを null にします。また、free を呼び出す前に呼び出すAssert(p != NULL);
ので、同じポインターを二重に解放しようとする試みをキャッチします。
私のコードは、(DEBUG ビルドのみ) メモリを割り当てた直後に明らかな値で満たすfree()
、ポインタのコピーがある場合 に呼び出す前に同じことを行うなど、他のことも行います。詳細はこちら。
編集:リクエストごとに、ここにサンプルコードがあります。
void
FreeAnything(void **pp)
{
void *p;
AssertWithMessage(pp != NULL, "need pointer-to-pointer, got null value");
if (!pp)
return;
p = *pp;
AssertWithMessage(p != NULL, "attempt to free a null pointer");
if (!p)
return;
free(p);
*pp = NULL;
}
// FOO is a typedef for a struct type
void
FreeInstanceOfFoo(FOO **pp)
{
FOO *p;
AssertWithMessage(pp != NULL, "need pointer-to-pointer, got null value");
if (!pp)
return;
p = *pp;
AssertWithMessage(p != NULL, "attempt to free a null FOO pointer");
if (!p)
return;
AssertWithMessage(p->signature == FOO_SIG, "bad signature... is this really a FOO instance?");
// free resources held by FOO instance
if (p->storage_buffer)
FreeAnything(&p->storage_buffer);
if (p->other_resource)
FreeAnything(&p->other_resource);
// free FOO instance itself
free(p);
*pp = NULL;
}
コメント:
2 番目の関数で、2 つのリソース ポインターをチェックして null でないかどうかを確認してから、 を呼び出す必要があることがわかりますFreeAnything()
。これは、 がassert()
ヌル ポインターについて文句を言うためです。ダブルフリーの試みを検出するためにそのアサートを持っていますが、実際に多くのバグをキャッチしたとは思いません。アサートを省略したい場合は、チェックを省略して常に呼び出すことができますFreeAnything()
。アサート以外に、null ポインターを解放しようとしても悪いことは何も起こりませんFreeAnything()
。これは、ポインターをチェックし、既に null である場合に返すだけだからです。
私の実際の関数名はかなり簡潔ですが、この例では自己文書化された名前を選択しようとしました。また、私の実際のコードでは、0xDC
呼び出す前にバッファーに値を入力するデバッグ専用コードがあるfree()
ため、同じメモリ (null にされないもの) への追加のポインターがある場合、データがそれが指しているのは偽のデータです。DEBUG_ONLY()
非デバッグ ビルドでは何もコンパイルされないマクロがあります。FILL()
構造体に対して aを実行するマクロsizeof()
。これら 2 つは同等に機能します:sizeof(FOO)
またはsizeof(*pfoo)
. したがって、ここにFILL()
マクロがあります:
#define FILL(p, b) \
(memset((p), b, sizeof(*(p)))
呼び出す前に値FILL()
を入れるために使用する例を次に示します。0xDC
if (p->storage_buffer)
{
DEBUG_ONLY(FILL(pfoo->storage_buffer, 0xDC);)
FreeAnything(&p->storage_buffer);
}
これを使用する例:
PFOO pfoo = ConstructNewInstanceOfFoo(arg0, arg1, arg2);
DoSomethingWithFooInstance(pfoo);
FreeInstanceOfFoo(&pfoo);
assert(pfoo == NULL); // FreeInstanceOfFoo() nulled the pointer so this never fires