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


276

どうやっmallocfree仕事をしたいのか知りたい。

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;
}

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


5
実際に使用するコンパイラとランタイムライブラリに依存する必要はありませんか?
Vilx- 2009

9
CRTの実装によって異なります。したがって、一般化することはできません。
ナビーン

58
そのstrcpyは8バイトではなく9バイトを書き込みます。NULLターミネータを忘れないでください;-)。
エヴァン・テラン


2
@LưuVĩnhPhúcC ++です。注意してくださいcout <<
ブレーデンベスト

回答:


385

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

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

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

したがって、40バイトをOSに返すだけでは機能しません。ではfreeは何をするのでしょうか?

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

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

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

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

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

これはかなり優雅な行動です。私はまた、どこかで暴走したポインターがメモリーフリーリストのデータを上書きし、システムがすぐにクラッシュせず、後でいくつかのサブルーチンが発生する状況を見ました。中程度の複雑さのシステムでさえ、そのような問題はデバッグするのが本当に、本当に難しいかもしれません!私が関係した1つのケースでは、クラッシュの原因を見つけるのに数日(開発者の大規模なグループ)がかかりました。それは、メモリダンプが示す場所とはまったく異なる場所にあったためです。それは時限爆弾のようなものです。次の "free"または "malloc"はクラッシュしますが、理由はわかりません。

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


63
free()がメモリをOSに返さない可能性があることに気づいていない人が多すぎます。それらを啓発するのを助けてくれてありがとう。
Artelius

Artelius:それどころか、常に新しい意志はありますか?
Guillaume07

3
@ Guillaume07私はあなたが新しいものではなく削除するつもりだったと思います。いいえ、必要ありません。削除と解放は(ほぼ)同じことを行います。MSVC2013でそれぞれが呼び出すコードは次のとおりです
Yay295

1
deleteは常にデストラクタを呼び出しますが、メモリ自体は後で割り当てるために空きリストに入る場合があります。実装によっては、mallocが使用するのと同じフリーリストになる場合もあります。
David C.

1
@Juergenしかし、free()がmallocから割り当てられたメモリの量に関する情報を含む追加のバイトを読み取ると、4が取得されます。
未定義の動作

56

aluserがこのフォーラムのスレッドで言っているように:

プロセスには、アドレスxからアドレスyまでのヒープと呼ばれるメモリ領域があります。すべてのmallocされたデータはこの領域にあります。malloc()は、ヒープ内のすべての空き領域の一部のデータ構造(リストなど)を保持します。mallocを呼び出すと、リストに対して十分な大きさのチャンクが検索され、そのチャンクへのポインターが返され、それが解放されなくなったという事実とその大きさが記録されます。同じポインタでfree()を呼び出すと、free()はそのチャンクの大きさを調べ、それをフリーチャンク()のリストに追加します。malloc()を呼び出し、ヒープ内に十分な大きさのチャンクが見つからない場合は、brk()システムコールを使用してヒープを拡張します。つまり、アドレスyを増やし、古いyと新しいyの間のすべてのアドレスを有効なメモリである。brk()はsyscallでなければなりません。

malloc()はシステム/コンパイラーに依存しているため、具体的な答えを出すのは困難です。ただし、基本的には、割り当てられているメモリを追跡し、その方法に応じて、解放の呼び出しが失敗または成功するようにします。

malloc() and free() don't work the same way on every O/S.


1
これが未定義の動作と呼ばれる理由です。1つの実装では、無効な書き込みの後でfreeを呼び出すと、悪魔が鼻から飛び出す可能性があります。あなたは、決して知らない。
Braden Best

36

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

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

25

メモリ保護にはページの細分性があり、カーネルの相互作用が必要になります

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

メモリは、ページ単位でのみプログラムから削除でき、それが確認されることはほとんどありません。

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

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

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

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

動作理論

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


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


いい答えだ。次の論文を推奨します:Dynamic Storage Allocation:アロケータが利用するヘッダーフィールドやフリーリストなどの内部メカニズムに関する詳細なレビューのためのWilsonらによる調査とクリティカルレビュー。
Goaler444

23

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

たとえば、4バイトを割り当てる場合、mallocは4バイトへのポインターを提供します。あなたが気付かないかもしれないことは、あなたが割り当てたすべてのメモリのチェーンを作るためあなたの4バイトの前の 8-12バイトのメモリがmallocによって使われているということです。freeを呼び出すと、ポインタが取得され、データのある場所にバックアップされ、その上で動作します。

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

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


12

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

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

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

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

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


12

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

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

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


6

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


5

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

mallocを実行すると、ブロックはフリーリストから取得されます。解放すると、ブロックは空きリストに戻されます。おそらく、ポインターの終わりを上書きすると、フリーリストのブロックのヘッダーに書き込むことになります。メモリを解放すると、free()は次のブロックを調べようとしますが、バスエラーを引き起こすポインタに到達する可能性があります。


4

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

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

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

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

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


3

自分のものではないメモリを使用したため、プログラムがクラッシュします。他の誰かが使用している場合とそうでない場合があります-運が良ければクラッシュし、そうでなければ問題は長い間隠されたまま戻ってきて後で噛みつくことがあります。

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

  • 新しいメモリを取得する方法
  • それを格納する方法-(リストまたは他の構造、異なるサイズのメモリチャンクの複数のリストなど)
  • ユーザーが現在利用可能なメモリよりも多くのメモリを要求した場合の対応(OSからより多くのメモリを要求し、既存のブロックのいくつかを結合し、それらを正確に結合する方法など)
  • ユーザーがメモリを解放したときの対処
  • デバッグアロケーターは、要求されたより大きなチャンクを与え、いくつかのバイトパターンを埋めることができます。メモリを解放すると、アロケーターはブロック外に書き込まれたかどうかをチェックできます(これはおそらくあなたの場合に起こります)...

2

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


1

これは単にで周りのプログラムブレークポインタを動かすことを認識することも重要だbrksbrk実際にはありません割り当てるメモリを、それだけでアドレス空間を設定します。たとえば、Linuxでは、アドレス範囲にアクセスすると、実際の物理ページによってメモリが「バッキング」されます。これにより、ページフォールトが発生し、最終的にカーネルがページアロケータを呼び出してバッキングページを取得します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.