私は講義で、ポインタでfree()
を2回呼び出すのは本当に本当に悪いことだと教えられてきました。ポインタを解放した直後にNULL
にポインタを設定することをお勧めします。
しかし、その理由についての説明はまだ聞いたことがありません。私が理解していることから、malloc()
の動作方法は、技術的に割り当てられ、使用するように指定されたポインターを追跡する必要があります。では、free()
を介して受け取ったポインターがまだ解放されているかどうかがわからないのはなぜですか?
以前に解放された場所でfree()
を呼び出すと、内部で何が起こるかを理解したいと思います。
malloc
を使用すると、ヒープ上のメモリ位置を自分用に予約するようにPCに指示します。コンピュータは、アドレス指定されたスペースの最初のバイトへのポインタを返します。
free
を使用すると、実際にはそのスペースはもう必要ないことをコンピューターに伝えているので、そのスペースを他のデータに使用できるものとしてマークします。
ポインタは引き続きそのメモリアドレスを指します。この時点で、ヒープ内の同じスペースを別のmalloc
呼び出しで返すことができます。もう一度free
を呼び出すと、以前のデータではなく新しいデータが解放されます。これはプログラムに適していない可能性があります;)
最初の質問に答えるには、
では、
free()
を介して受け取ったポインタがまだ解放されているかどうかがわからないのはなぜですか?
なぜなら、C標準のmalloc()
の仕様はこれを義務付けていないからです。 malloc()
または関数ファミリーを呼び出すと、ポインターが返され、内部的に割り当てられたメモリ位置のサイズが格納されますinそのポインター。これが、free()
がメモリをクリーンアップするためのサイズを必要としない理由です。
また、free()
- dを実行すると、実際に割り当てられたメモリで何が起こるかは、実装に依存します。 free()
の呼び出しは、割り当てられたメモリがプロセスで使用されなくなったことを示すためのmarkerであり、必要に応じて再利用して再割り当てできます。したがって、割り当てられたポインタを追跡することは、その時点では非常に不要です。 allバックトラックを維持することはOSに不必要な負担になります。
ただし、デバッグの目的で、DUMAやdmallocなどの一部のライブラリ実装でこのジョブを実行できます。最後になりましたが、Valgrindのmemcheckツールです。
さて、技術的に、すでに解放されたポインタでfree()
を呼び出した場合、C
標準は動作を指定しません。 未定義の振る舞い です。
_C11
_、§7.22.3.3章、free()
function
[...]引数がメモリ管理関数によって以前に返されたポインタと一致しない場合、またはスペースが
free()
またはrealloc()
の呼び出しによって割り当て解除された場合、動作は未定義です。
C標準では、free
とそのファミリ関数によって返されるポインタでmalloc
を2回呼び出すと、未定義の動作が呼び出されるとだけ言われています。なぜそうなのか、これ以上の説明はありません。
しかし、なぜそれが悪いのかが説明されています ここ :
同じチャンクを2回解放する
この種のエラーが何を引き起こす可能性があるかを理解するには、メモリマネージャが通常どのように機能するかを覚えておく必要があります。多くの場合、割り当てられたチャンクのサイズを、チャンク自体の直前にメモリに格納します。メモリを解放した場合、このメモリチャンクは別の
malloc()
リクエストによって再度割り当てられた可能性があるため、このdouble-freeは実際には間違ったメモリチャンクを解放します-アプリケーションのどこかにダングリングポインタがあります。このようなバグは、コード内で発生した場所よりもはるかに遅れて現れる傾向があります。まったく見えないこともありますが、醜い頭を抱える機会を待って、まだ潜んでいます。発生する可能性のある別の問題は、このdouble-freeが、解放されたチャンクが隣接する空きチャンクとマージされて、より大きな空きチャンクを形成し、次に大きなチャンクを形成した後に実行されることです再割り当てされました。このような場合、2回目にチャンクを
free()
しようとすると、実際には、アプリケーションが現在使用しているメモリチャンクの一部のみが解放されます。これにより、さらに予期しない問題が発生します。
malloc
を呼び出すと、ポインターを取得します。ランタイムライブラリは、malloc
edメモリを追跡する必要があります。通常、malloc
は、malloc
edメモリから分離されたメモリ管理構造を1か所に格納します。したがって、xバイトのmalloc
は実際にはx + nバイトを取ります。1つの可能なレイアウトは、最初のnバイトに次の(そしておそらく前の)割り当てられたメモリブロックへのポインタを持つリンクリスト構造体が含まれていることです。
ポインタをfree
すると、関数free
はその内部メモリ管理構造をウォークスルーし、渡したポインタがmalloc
された有効なポインタであるかどうかを確認できます。そうして初めて、メモリブロックの隠された部分にアクセスできます。ただし、このチェックを行うには、特に多くを割り当てる場合、非常に時間がかかります。したがって、free
は、単に有効なポインタを渡すことを前提としています。つまり、メモリブロックの非表示部分に直接アクセスし、そこにあるリンクリストポインタが有効であると想定します。
ブロックを2回free
すると、誰かが新しいmalloc
を実行し、解放したばかりのメモリを取得して上書きし、2番目のfree
が無効なポインタを読み取るという問題が発生する可能性があります。それから。
free
dポインタをNULL
に設定すると、デバッグに役立つため、良い習慣です。 free
dメモリにアクセスすると、プログラムがクラッシュする可能性がありますが、疑わしい値を読み取るだけで、後でクラッシュする可能性もあります。その場合、根本原因を見つけるのは難しいかもしれません。 free
dポインタをNULL
に設定すると、メモリにアクセスしようとするとプログラムがすぐにクラッシュします。これは、デバッグ中に非常に役立ちます。