301

malloc仕組みやfree働き方を知りたい

int main() {
    unsigned char *p = (unsigned char*)malloc(4*sizeof(unsigned char));
    memset(p,0,4);
    strcpy((char*)p,"abcdabcd"); // **deliberately storing 8bytes**
    cout << p;
    free(p); // Obvious Crash, but I need how it works and why crash.
    cout << p;
    return 0;
}

可能であれば、答えがメモリレベルで詳細に記載されている場合は、本当に感謝しています。

4

13 に答える 13

420

OK mallocに関するいくつかの回答はすでに投稿されています。

さらに興味深いのは、free がどのように機能するかです(この方向では、malloc もよりよく理解できます)。

多くの malloc/free 実装では、free は通常、メモリをオペレーティング システムに返しません (または、少なくともまれなケースに限られます)。その理由は、ヒープにギャップが生じるため、2 GB または 4 GB の仮想メモリをギャップで締めくくってしまう可能性があるためです。仮想メモリが終了するとすぐに、非常に大きな問題が発生するため、これは避ける必要があります。もう 1 つの理由は、OS が特定のサイズと配置のメモリ チャンクしか処理できないことです。具体的に言うと、通常、OS は仮想メモリ マネージャが処理できるブロック (ほとんどの場合、512 バイトの倍数、たとえば 4KB) のみを処理できます。

そのため、OS に 40 バイトを返すことはできません。では、無料は何をするのでしょうか?

Free は、メモリ ブロックを独自の空きブロック リストに入れます。通常、アドレス空間内の隣接するブロックを融合しようとします。フリー ブロック リストは、最初に管理データを含むメモリ チャンクの単なる循環リストです。これは、標準の malloc/free で非常に小さなメモリ要素を管理することが効率的でない理由でもあります。すべてのメモリ チャンクには追加のデータが必要であり、サイズが小さいほど断片化が発生します。

free-list は、新しいメモリ チャンクが必要なときに malloc が最初に確認する場所でもあります。OS から新しいメモリを要求する前にスキャンされます。必要なメモリよりも大きなチャンクが見つかった場合、チャンクは 2 つの部分に分割されます。1 つは呼び出し元に返され、もう 1 つは空きリストに戻されます。

この標準的な動作には、さまざまな最適化があります (たとえば、メモリの小さなチャンクの場合)。しかし、malloc と free は非常に普遍的なものでなければならないため、代替手段が使用できない場合は、標準の動作が常にフォールバックになります。フリーリストの処理にも最適化があります。たとえば、サイズでソートされたリストにチャンクを格納します。ただし、すべての最適化には独自の制限もあります。

コードがクラッシュする理由:

その理由は、9 文字 (末尾の null バイトを忘れないでください) を 4 文字のサイズの領域に書き込むことによって、データのチャンクの「背後」にある別のメモリ チャンクに保存されている管理データをおそらく上書きするからです (ほとんどの場合、このデータはメモリ チャンクの「前」に格納されるためです)。その後、free がチャンクをフリー リストに入れようとすると、この管理データに触れて、上書きされたポインターに遭遇する可能性があります。これにより、システムがクラッシュします。

これはかなり優雅な振る舞いです。どこかのランナウェイ ポインタがメモリ フリー リストのデータを上書きし、システムがすぐにはクラッシュせず、後でいくつかのサブルーチンがクラッシュする状況も見てきました。中程度の複雑さのシステムであっても、このような問題をデバッグするのは非常に困難です。私が関与した 1 つのケースでは、メモリ ダンプで示された場所とはまったく異なる場所にあったため、クラッシュの原因を特定するのに数日かかりました。時限爆弾のようなものです。次の「free」または「malloc」がクラッシュすることはわかっていますが、その理由はわかりません!

これらは C/C++ の最悪の問題の一部であり、ポインターが非常に問題になる理由の 1 つです。

于 2009-07-13T13:06:06.860 に答える
43

malloc/free の 1 つの実装は、次のことを行います。

  1. sbrk() (Unix 呼び出し) を介して OS からメモリのブロックを取得します。
  2. サイズ、権限、次のブロックと前のブロックの場所などの情報を使用して、そのメモリ ブロックの周りにヘッダーとフッターを作成します。
  3. malloc が呼び出されると、適切なサイズのブロックを指すリストが参照されます。
  4. その後、このブロックが返され、それに応じてヘッダーとフッターが更新されます。
于 2009-07-13T12:29:09.780 に答える
30

メモリ保護にはページ粒度があり、カーネルの対話が必要になります

サンプルコードは基本的に、サンプルプログラムがトラップしない理由を尋ねます。答えは、メモリ保護はカーネル機能であり、ページ全体にのみ適用されるのに対し、メモリアロケータはライブラリ機能であり、..強制なしで..任意に管理するということです.多くの場合、ページよりもはるかに小さいサイズのブロック。

プログラムからメモリを削除できるのはページ単位だけであり、それも観察される可能性はほとんどありません。

calloc(3) と malloc(3) は、必要に応じてカーネルと対話してメモリを取得します。しかし、 free(3) のほとんどの実装はカーネルにメモリを返さず1、解放されたブロックを再利用するために calloc() と malloc() が後で参照する空きリストにメモリを追加するだけです。

free() がシステムにメモリを返したい場合でも、カーネルが実際に領域を保護するためには、少なくとも 1 つの連続したメモリ ページが必要です。ページの最後の小さなブロック。

あなたのブロックはそこにあり、空きリストに載っています。ほとんどの場合、まだ割り当てられているかのように、そのメモリと近くのメモリにアクセスできます。C はマシン コードに直接コンパイルされ、特別なデバッグの取り決めがなければ、ロードとストアのサニティ チェックは行われません。現在、空きブロックにアクセスしようとすると、ライブラリの実装者に不当な要求をしないようにするために、動作は標準では定義されていません。解放されたメモリまたは割り当てられたブロックの外側のメモリにアクセスしようとすると、さまざまな問題が発生する可能性があります。

  • アロケーターはメモリの個別のブロックを維持する場合もあれば、ブロックの直前または直後に割り当てたヘッダー (「フッター」だと思います) を使用する場合もありますが、空きリストを保持する目的でブロック内のメモリを使用したいだけかもしれません。一緒にリンクされています。その場合、ブロックの読み取りは問題ありませんが、その内容が変更される可能性があり、ブロックへの書き込みにより、アロケーターが誤動作またはクラッシュする可能性があります。
  • 当然、ブロックは将来割り当てられる可能性があり、その後、コードまたはライブラリ ルーチンによって上書きされるか、calloc() によってゼロで上書きされる可能性があります。
  • ブロックが再割り当てされると、サイズも変更される可能性があり、その場合、さらに多くのリンクまたは初期化がさまざまな場所に書き込まれます。
  • 明らかに、プログラムのカーネル既知のセグメントの 1 つの境界を越えるほど範囲外を参照する可能性があり、この 1 つのケースではトラップされます。

動作原理

したがって、あなたの例から全体的な理論に逆戻りすると、 malloc(3) は必要なときにカーネルからメモリを取得します。通常はページ単位でメモリを取得します。これらのページは、プログラムの必要に応じて分割または統合されます。malloc と free は連携してディレクトリを維持します。大きなブロックを提供できるようにするために、可能な場合は隣接する空きブロックを結合します。ディレクトリは、リンクされたリストを形成するために、解放されたブロックのメモリを使用する場合としない場合があります。(別の方法は、もう少し共有メモリとページングに適していて、ディレクトリ専用のメモリを割り当てる必要があります。) malloc と free は、特別なオプションのデバッグ コードがコンパイルされている場合でも、個々のブロックへのアクセスを強制する機能はほとんどありません。プログラム。


1. システムにメモリを返そうとする free() の実装がほとんどないという事実は、必ずしも実装者が怠けているためではありません。カーネルとのやり取りは、単にライブラリ コードを実行するよりもはるかに遅く、メリットはわずかです。ほとんどのプログラムでは、定常状態または増加するメモリ フットプリントがあるため、ヒープを分析して戻り可能なメモリを探すのに費やす時間は完全に無駄になります。その他の理由としては、内部の断片化により、ページに揃えられたブロックが存在する可能性が低くなり、ブロックを返すとブロックがいずれかの側に断片化される可能性が高いという事実が含まれます。最後に、大量のメモリを返す少数のプログラムは、malloc() をバイパスし、とにかくページを割り当てて解放するだけです。

于 2011-04-03T21:10:20.567 に答える
23

理論的には、malloc はオペレーティング システムからこのアプリケーションのメモリを取得します。ただし、4 バイトだけが必要な場合があり、OS はページ (多くの場合 4k) で動作する必要があるため、malloc はそれ以上のことを行います。ページを取得し、そこに独自の情報を配置して、そのページから割り当ておよび解放したものを追跡できるようにします。

たとえば、4 バイトを割り当てると、malloc は 4 バイトへのポインターを返します。気付いていないかもしれませんが、割り当てたすべてのメモリのチェーンを作成するために、4 バイトの前の 8 ~ 12 バイトのメモリが malloc によって使用されています。free を呼び出すと、ポインターが取得され、データがある場所に戻り、その上で動作します。

メモリを解放すると、malloc はそのメモリ ブロックをチェーンから外します...そして、そのメモリをオペレーティング システムに返す場合と返さない場合があります。その場合、OS がその場所へのアクセス許可を奪うため、そのメモリへのアクセスはおそらく失敗します。malloc がメモリを保持している場合(そのページに他のものが割り当てられているため、または何らかの最適化のために)、アクセスはたまたま機能します。まだ間違っていますが、うまくいくかもしれません。

免責事項: 私が説明したのは malloc の一般的な実装ですが、決して可能な唯一のものではありません。

于 2009-07-13T12:31:36.177 に答える
12

malloc() と free() がどのように機能するかは、使用するランタイム ライブラリによって異なります。通常、malloc() は、オペレーティング システムからヒープ (メモリのブロック) を割り当てます。malloc() への各要求は、このメモリの小さなチャンクを割り当て、呼び出し元にポインターを返します。メモリ割り当てルーチンは、割り当てられたメモリ ブロックに関する追加情報を格納して、ヒープ上の使用済みメモリと空きメモリを追跡できるようにする必要があります。この情報は、多くの場合、malloc() によって返されるポインタの直前の数バイトに格納され、メモリ ブロックのリンクされたリストにすることができます。

malloc() によって割り当てられたメモリ ブロックを超えて書き込むと、残りの未使用のメモリ ブロックである可能性がある次のブロックの簿記情報の一部が破壊される可能性があります。

プログラムがクラッシュする可能性のある場所の 1 つは、バッファーにコピーする文字が多すぎる場合です。余分な文字がヒープの外側にある場合、存在しないメモリに書き込もうとするため、アクセス違反が発生する可能性があります。

于 2009-07-13T12:35:23.403 に答える
12

NUL ターミネータのため、strcpy 行は 8 バイトではなく 9 バイトを格納しようとします。未定義の動作を引き起こします。

free の呼び出しは、クラッシュする場合とクラッシュしない場合があります。割り当ての 4 バイトの「後」のメモリは、C または C++ の実装によって別の用途に使用される可能性があります。それ以外の用途に使用されている場合は、その「他の用途」に悪影響を及ぼす可能性がありますが、それ以外の用途に使用されていない場合は、たまたまそれを回避することができます。「それを回避する」は良いように聞こえるかもしれませんが、実際には悪いことです。コードが正常に実行されているように見えても、将来の実行では回避できない可能性があることを意味するからです。

デバッグ スタイルのメモリ アロケータを使用すると、特別なガード値がそこに書き込まれていることがわかり、 free がその値をチェックし、見つからない場合はパニックになります。

そうしないと、次の 5 バイトに、まだ割り当てられていない他のメモリ ブロックに属するリンク ノードの一部が含まれていることがわかる場合があります。ブロックを解放するには、使用可能なブロックのリストにブロックを追加する必要があり、リスト ノードに落書きしたため、その操作で無効な値を持つポインターが逆参照され、クラッシュが発生する可能性があります。

それはすべてメモリ アロケータに依存します。異なる実装では異なるメカニズムが使用されます。

于 2009-07-13T12:32:37.597 に答える
6

これは特に malloc や free とは関係ありません。文字列をコピーした後、プログラムは未定義の動作を示します。その時点またはその後の任意の時点でクラッシュする可能性があります。これは、malloc と free を使用したことがなく、スタックまたは静的に char 配列を割り当てた場合でも当てはまります。

于 2009-07-13T12:29:17.830 に答える
5

malloc と free は実装に依存します。典型的な実装では、使用可能なメモリを「空きリスト」 (使用可能なメモリ ブロックのリンクされたリスト) に分割します。多くの実装では、人為的に小さなオブジェクトと大きなオブジェクトに分割しています。空きブロックは、メモリ ブロックの大きさや次のメモリ ブロックの場所などに関する情報から始まります。

malloc すると、フリー リストからブロックが取り出されます。解放すると、ブロックは解放リストに戻されます。ポインターの末尾を上書きする場合、空きリスト内のブロックのヘッダーに書き込みを行っている可能性があります。メモリを解放すると、free() は次のブロックを調べようとし、最終的にバス エラーを引き起こすポインターにヒットする可能性があります。

于 2009-07-13T12:34:12.933 に答える
4

それは、メモリ アロケータの実装と OS に依存します。

たとえば、ウィンドウの下では、プロセスは 1 ページ以上の RAM を要求できます。次に、OS はそれらのページをプロセスに割り当てます。ただし、これはアプリケーションに割り当てられたメモリではありません。CRT メモリ アロケータは、メモリを連続した "使用可能な" ブロックとしてマークします。次に、CRT メモリ アロケータは空きブロックのリストを調べて、使用できる最小のブロックを見つけます。次に、そのブロックを必要なだけ取得し、「割り当てられた」リストに追加します。実際のメモリ割り当ての先頭にはヘッダが付きます。このヘッダーには、さまざまな情報が含まれます (たとえば、リンクされたリストを形成するために、次に割り当てられたブロックと前に割り当てられたブロックを含むことができます。おそらく、割り当てのサイズが含まれます)。

次に、Free はヘッダーを削除し、空きメモリ リストに追加し直します。周囲の空きブロックと一緒に大きなブロックを形成する場合、これらは一緒に追加されて、より大きなブロックになります。ページ全体が解放された場合、アロケーターはおそらくそのページを OS に返します。

単純な問題ではありません。OS アロケータ部分は完全に制御できません。Doug Lea の Malloc (DLMalloc) などを読んで、かなり高速なアロケーターがどのように機能するかを理解することをお勧めします。

編集:割り当てよりも大きく書き込むことで、次のメモリヘッダーを上書きしたという事実が原因で、クラッシュが発生します。このように解放すると、正確に何が解放され、次のブロックにどのようにマージされるかについて非常に混乱します。これにより、無料ですぐにクラッシュが発生するとは限りません。後でクラッシュする可能性があります。一般に、メモリの上書きは避けてください。

于 2009-07-13T12:32:33.823 に答える
3

自分のものではないメモリを使用したため、プログラムがクラッシュしました。それは他の誰かによって使用されているかどうかにかかわらず、運が良ければクラッシュします。

malloc/free の実装に関する限り、本全体がこのトピックに専念しています。基本的に、アロケータは OS からより大きなメモリ チャンクを取得し、それらを管理します。アロケーターが対処しなければならない問題のいくつかは次のとおりです。

  • 新しいメモリを取得する方法
  • 保存方法 - (リストまたはその他の構造、異なるサイズのメモリ チャンクの複数のリストなど)
  • ユーザーが現在利用可能なメモリよりも多くのメモリを要求した場合の対処方法 (OS からより多くのメモリを要求する、既存のブロックのいくつかを結合する、それらを正確に結合する方法など)。
  • ユーザーがメモリを解放したときの対処方法
  • アロケーターをデバッグすると、要求したよりも大きなチャンクが得られ、バイトパターンが満たされる場合があります。メモリを解放すると、アロケーターはブロックの外側に書き込まれたかどうかを確認できます(これはおそらくあなたのケースで発生しています)...
于 2009-07-13T12:44:50.287 に答える
2

実際の動作はコンパイラ/ランタイムによって異なるため、なんとも言えません。デバッグ/リリース ビルドでも動作が異なります。VS2005 のデバッグ ビルドでは、割り当ての間にマーカーを挿入してメモリの破損を検出するため、クラッシュの代わりに free() でアサートします。

于 2009-07-13T12:31:01.687 に答える