66

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

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

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

Unhandled exception エラー メッセージでクラッシュします。free がなぜ、どのように機能するのかを理解したいので、使い方だけでなく、奇妙なエラーや例外を理解し、コードをより適切にデバッグできるようになります。

どうもありがとう

4

8 に答える 8

122

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

ポインタを解放すると、そのアドレスを使用して、割り当てられたブロックの先頭(通常)に追加された特別な情報が検索されます。別のアドレスを渡すと、ガベージを含むメモリにアクセスするため、その動作は定義されていません(ただし、ほとんどの場合、クラッシュが発生します)

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

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

これが、ポインタが指すメモリを解放した後にポインタを使用しないようにすることが重要である理由です。このためのベストプラクティスは、メモリを解放した後にポインタをNULLに設定することです。これは、NULLを簡単にテストできるためです。 NULLポインタを介してメモリにアクセスしようとすると、動作は悪くなりますが一貫性があり、デバッグがはるかに簡単になります。

于 2009-12-24T06:55:04.690 に答える
28

おそらく、受け取ったポインターを正確に返す必要があることを知っているでしょう。

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

最終的に、アロケーターはブロックに関するメタデータを持っている必要があります。少なくとも、長さをどこかに保存しておく必要があります。

その方法を3つ紹介します。

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

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

  • 実装では、住所から一部の情報を取得し、地図から一部の情報を取得できます。4.3BSD カーネル アロケータ ( 「McKusick-Karel アロケータ」と呼ばれていると思います) は、ページ サイズ未満のオブジェクトに対して 2 の累乗の割り当てを行い、ページごとのサイズのみを保持して、特定のページからすべての割り当てを行います。シングルサイズ。

いくつかのタイプの 2 番目のアロケータと、おそらく任意の種類の 3 番目のタイプのアロケータを使用して、実際にポインタとDTRTを進めたことを検出できますが、そのためにランタイムを焼き尽くす実装があるかどうかは疑問です。

于 2009-12-24T07:43:47.993 に答える
14

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

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

このポインタを呼び出すとfree、システムは4バイトを逆方向に検索して、最初に14バイトが割り当てられたことを確認し、解放する量を認識します。このシステムは、それ自体への追加パラメーターとして解放するデータの量を提供することを防ぎますfree

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

于 2009-12-24T06:53:54.220 に答える
8

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

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

于 2009-12-24T06:53:12.707 に答える
7

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

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

于 2009-12-24T06:52:33.667 に答える
5

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

于 2009-12-24T07:24:47.980 に答える
3

絶対にしないでください。

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

于 2010-02-17T06:23:28.467 に答える