web-dev-qa-db-ja.com

Cでfreeとmallocはどのように機能しますか?

たとえば、「真ん中から」ポインタを解放しようとするとどうなるかを理解しようとしています。次のコードを見てください。

char *ptr = (char*)malloc(10*sizeof(char));

for (char i=0 ; i<10 ; ++i)
{
    ptr[i] = i+10;
}
++ptr;
++ptr;
++ptr;
++ptr;
free(ptr);

未処理の例外エラーメッセージでクラッシュします。なぜ無料で機能するのかを理解したいので、使い方だけでなく、奇妙なエラーや例外を理解し、コードをより良くデバッグできるようになりますץ

どうもありがとう

57
user238082

ブロックをmallocすると、実際には要求よりも少し多くのメモリが割り当てられます。この追加メモリは、割り当てられたブロックのサイズ、ブロックチェーン内の次の空き/使用済みブロックへのリンク、およびシステムが過去の書き込みを検出するのに役立つ「ガードデータ」などの情報を格納するために使用されます割り当てられたブロックの終わり。また、ほとんどのアロケーターは、合計サイズやメモリの一部をバイトの倍数に切り上げます(たとえば、64ビットシステムでは、データを64ビットの倍数(8バイト)に揃えることができます。アライメントされていないアドレスからデータにアクセスすることは、プロセッサ/バスにとってより困難で非効率的です)。したがって、「パディング」(未使用バイト)が発生することもあります。

ポインターを解放すると、ポインターはそのアドレスを使用して、割り当てられたブロックの先頭(通常)に追加した特別な情報を見つけます。別のアドレスを渡すと、ガベージを含むメモリにアクセスするため、その動作は未定義です(ただし、最も頻繁にクラッシュします)

後で、ブロックをfree()してもポインタを「忘れない」場合、将来そのポインタを介して誤ってデータにアクセスしようとする可能性があり、動作は未定義です。次のいずれかの状況が発生する可能性があります。

  • メモリは空きブロックのリストに入れられる可能性があるため、メモリにアクセスすると、そこに残ったデータが含まれており、コードは正常に実行されます。
  • メモリアロケータがプログラムの別の部分にメモリ(の一部)を与えた可能性があり、それによっておそらく古いデータ(の一部)が上書きされるため、それを読み取ると、予期しない動作を引き起こす可能性のあるゴミが発生しますまたはコードからクラッシュします。または、他のデータを上書きして、プログラムの他の部分が将来のある時点で奇妙な動作をするようにします。
  • メモリがオペレーティングシステムに返された可能性があります(使用しなくなったメモリの「ページ」はアドレススペースから削除できるため、そのアドレスで使用可能なメモリはなくなります-基本的には未使用の「ホール」アプリケーションのメモリ内)。アプリケーションがデータにアクセスしようとすると、ハードメモリ障害が発生し、プロセスが強制終了されます。

これが、ポインタが指すメモリを解放した後にポインタを使用しないことを確認することが重要である理由です-これを行うためのベストプラクティスは、メモリを解放した後にポインタをNULLに設定することです。 NULLポインターを介してメモリにアクセスしようとすると、悪いがconsistentの動作が発生し、デバッグがはるかに容易になります。

101
Jason Williams

おそらく、受け取ったポインターを正確に戻すことになっていることをご存じでしょう。

Free()は最初はブロックの大きさを知らないため、アドレスから元のブロックを識別して空きリストに戻すために補助情報が必要です。また、より価値のある大きなフリーブロックを生成するために、小さなフリーブロックをネイバーとマージしようとします。

最終的に、アロケータにはブロックに関するメタデータが必要です。少なくとも、どこかに長さを格納する必要があります。

これを行う3つの方法を説明します。

  • 1つの明らかな場所は、返されたポインターの直前に保存することです。要求よりも数バイト大きいブロックを割り当て、最初のWordにサイズを保存してから、2番目のWordへのポインターを返すことができます。

  • もう1つの方法は、アドレスをキーとして使用して、少なくとも割り当てられたブロックの長さを記述する別のマップを保持することです。

  • 実装は、アドレスからの情報とマップからの情報を引き出すことができます。 4.3BSDカーネルアロケーター( "McKusick-Karelアロケーター"と呼ばれる)は、ページ未満のオブジェクトに対して2のべき乗の割り当てを行いますサイズとページごとのサイズのみを保持し、特定のページからすべての割り当てを単一サイズにします。

いくつかのタイプの2番目とおそらく3番目のタイプのアロケーターでは、ポインターと [〜#〜] dtrt [〜#〜] を進めたことを実際に検出することが可能です。実装がそれを行うためにランタイムを燃やすかどうかは疑問です。

25
DigitalRoss

ほとんどの(すべてではないにしても)実装は、操作している実際のポインターの前に数バイトを解放するためにデータ量を検索します。ワイルドfreeを実行すると、メモリマップが破損します。

たとえば、10バイトのメモリを割り当てる場合、システムは実際に予約します。たとえば、14です。最初の4には、要求したデータ量(10)が含まれ、mallocの戻り値は割り当てられた14の未使用データの最初のバイトへのポインター。

このポインターでfreeを呼び出すと、システムは4バイトを後方にルックアップして、最初に14バイトを割り当てたことを知り、解放する量を認識します。このシステムでは、free自体に追加のパラメーターとして解放するデータ量を提供できません。

もちろん、malloc/freeの他の実装では、これを達成するために他の方法を選択できます。ただし、通常、freeまたは同等の関数によって返されるものとは異なるポインターでmallocをサポートしません。

10
Zeograd

から http://opengroup.org/onlinepubs/007908775/xsh/free.html

Free()関数は、ptrが指すスペースの割り当てを解除します。つまり、さらに割り当てられるようになります。 ptrがNULLポインターの場合、アクションは発生しません。そうではなく、引数がcalloc()、malloc()、realloc()またはvalloc()関数によって以前に返されたポインターと一致しない場合、またはfree()またはrealloc()の呼び出しによってスペースが解放された場合、動作は未定義です。空きスペースを参照するポインターを使用すると、未定義の動作が発生します。

8
PetrosB

それは未定義の動作です-しないでください。 free()から取得したmalloc()ポインターのみ、それ以前に調整することはありません。

問題はfree()は非常に高速でなければならないため、調整されたアドレスが属する割り当てを見つけようとせず、調整されたアドレスのブロックをヒープに返そうとします。これは未定義の動作につながります-通常、ヒープの破損またはプログラムのクラッシュ。

7
sharptooth

間違ったアドレスを解放しています。 ptrの値を変更することにより、アドレスを変更します。 freeは、4バイト前からブロックを解放しようとすることを知る方法がありません。元のポインターをそのまま保持し、操作されたポインターの代わりにそれを解放します。他の人が指摘したように、あなたがしていることをした結果は「未定義」です...したがって未処理の例外です。

5
Jason D

これは絶対にしないでください。

間違ったアドレスを解放しています。 ptrの値を変更することにより、アドレスを変更します。 freeは、4バイト前からブロックを解放しようとすることを知る方法がありません。元のポインターをそのまま保持し、操作されたポインターの代わりにそれを解放します。他の人が指摘したように、あなたがやっていることをした結果は「未定義」です...したがって、未処理の例外です

2
Jeet

本から: Cポインターの理解と使用

メモリが割り当てられると、ヒープマネージャによって維持されるデータ構造の一部として追加情報が保存されます。この情報には、とりわけブロックのサイズが含まれ、通常、割り当てられたブロックのすぐ隣に配置されます。

2
Koray Tugay