私はコンピューター工学を勉強していて、いくつかの電子工学のコースがあります。 (これらのコースの)2人の教授から、free()
関数(malloc()
、calloc()
などの後)の使用を避けることができると聞いた割り当てられたメモリ空間が他のメモリを割り当てるために再び使用される可能性は低いためです。つまり、たとえば、4バイトを割り当ててから解放すると、4バイトのスペースが再び割り当てられなくなる可能性があります。つまり、holeになります。
クレイジーだと思います。ヒープを解放せずにヒープにメモリを割り当てるnot-toy-programを使用することはできません。しかし、各malloc()
にfree()
が必要なほど重要である理由を正確に説明する知識はありません。
だから:malloc()
を使用せずにfree()
を使用することが適切である状況はありますか?そうでない場合、どのようにこれを教授に説明できますか?
簡単:ほぼ真面目なmalloc()/free()
実装のソースを読むだけです。これにより、実際の メモリマネージャ は呼び出しの作業を処理します。これは、ランタイムライブラリ、仮想マシン、またはオペレーティングシステムにある可能性があります。もちろん、コードはすべての場合に等しくアクセスできるわけではありません。
隣接する穴を大きな穴に結合することにより、メモリが断片化されないようにすることは非常に一般的です。より深刻なアロケーターは、より深刻な手法を使用してこれを保証します。
したがって、3つの割り当てと割り当て解除を行い、この順序でメモリ内にブロックを配置するとします。
+-+-+-+
|A|B|C|
+-+-+-+
個々の割り当てのサイズは重要ではありません。次に、最初と最後のAとCを解放します。
+-+-+-+
| |B| |
+-+-+-+
最終的にBを解放すると、(最初は、少なくとも理論的には)次のようになります。
+-+-+-+
| | | |
+-+-+-+
これを最適化することができます
+-+-+-+
| |
+-+-+-+
つまり、単一のより大きなフリーブロックで、フラグメントは残っていません。
要求に応じた参照:
heap4.c
FreeRTOSのコード 。それはまったくナンセンスです。たとえば、malloc
のさまざまな実装があり、一部は Doug Lea's または this oneのようにヒープをより効率的にしようとします。
教授は万が一POSIXで働いていますか?小さくてミニマルなシェルアプリケーションをたくさん書くのに慣れているなら、それはこのアプローチがそれほど悪くないことを想像できるシナリオです-OSの余暇でヒープ全体を一度に解放するis 1000個の変数を解放するよりも高速です。アプリケーションが1〜2秒実行されると予想される場合、割り当て解除をまったく行わずに簡単に逃げることができます。
もちろん、それはまだ悪い習慣です(パフォーマンスの向上は、漠然とした直感ではなく、常にプロファイリングに基づいている必要があります)、他の制約を説明せずに学生に言うべきことではありませんが、多くの小さな配管シェルを想像できます-この方法で作成されるアプリケーション(静的割り当てを完全に使用しない場合)。変数を解放しないことでメリットが得られるものに取り組んでいる場合、極端に低遅延の状態で作業している場合(その場合、動的割り当てとC++をどうすれば余裕がありますか?:D)何か非常に間違ったこと(メモリの単一ブロックではなく、1000個の整数を次々に割り当てることによる整数配列の割り当てなど)。
あなたは彼らが電子工学の教授だと言いました。それらはファームウェア/リアルタイムソフトウェアの作成に使用される可能性があり、何かの実行がしばしば必要とされることを正確に計ることができました。これらの場合、すべての割り当てに十分なメモリがあることを知っていて、メモリを解放および再割り当てしないと、実行時間の制限がより簡単に計算される場合があります。
一部のスキームでは、ハードウェアメモリ保護を使用して、割り当てられたメモリでルーチンが完了することを確認したり、very例外的なケースでトラップを生成したりすることもできます。
あなたの教授は間違っていませんが、そうでもあります(少なくとも誤解を招いたり、単純化しすぎている)。メモリの断片化は、パフォーマンスとメモリの効率的な使用に問題を引き起こすため、場合によってはそれを考慮し、回避するためのアクションを実行する必要があります。 1つの古典的なトリックは、同じサイズの多くのものを割り当て、起動時にそのサイズの倍数であるメモリのプールを取得し、その使用を完全に内部的に管理する場合、断片化が発生しないようにすることですOSレベル(および内部メモリマッパーの穴は、このタイプの次のオブジェクトにぴったりのサイズになります)。
サードパーティ製のライブラリはすべて、この種の処理を行うだけであり、許容可能なパフォーマンスと実行速度が非常に遅い場合の違いです。 malloc()
およびfree()
の実行にはかなりの時間がかかります。これらを頻繁に呼び出すと、気づき始めます。
したがって、malloc()
とfree()
の単純な使用を回避することで、断片化とパフォーマンスの両方の問題を回避できます。しかし、それを理解したら、必ずfree()
すべてのmalloc()
は、特に理由がない限り非常に適切です。内部メモリプールを使用している場合でも、優れたアプリケーションは、終了する前にプールメモリをfree()
します。はい、OSはそれをクリーンアップしますが、アプリケーションのライフサイクルが後で変更された場合、プールがまだぶら下がっていることを忘れがちです...
もちろん、長時間実行されるアプリケーションは、割り当てられたすべてのものをクリーンアップまたはリサイクルすることについて完全に慎重である必要があります。そうしないと、最終的にメモリ不足になります。
質問の中で述べられている主張は、プログラマーの立場から文字通り解釈された場合、ナンセンスであると思いますが、オペレーティングシステムの観点からは真実(少なくとも一部)を持っています。
最終的に、malloc()はmmap()またはsbrk()を呼び出して、OSからページを取得します。
重要なプログラムでは、割り当てられたメモリのほとんどをfree()しても、プロセスの存続期間中にこのページがOSに戻される可能性は非常に小さくなります。したがって、free()されたメモリは、ほとんどの場合同じプロセスでのみ使用できますが、他のプロセスでは使用できません。
これを以前のコメント者や回答とは異なる角度からとると、可能性の1つは、教授がメモリが静的に割り当てられたシステム(つまり、プログラムがコンパイルされたとき)の経験があることです。
静的割り当ては、次のようなことを行うときに発生します。
define MAX_SIZE 32
int array[MAX_SIZE];
多くのリアルタイムおよび組み込みシステム(EEまたはCEが遭遇する可能性が最も高いシステム)では、通常、動的メモリ割り当てを完全に回避することが望ましいです。そのため、malloc
、new
、およびそれらの削除対応物の使用はまれです。さらに、コンピューターのメモリは近年爆発的に増加しています。
512 MBの空き容量があり、静的に1 MBを割り当てた場合、ソフトウェアが爆発する前に約511 MBを試してみる必要があります(まあ、正確ではありませんが、ここで説明します)。 511 MBを悪用すると仮定すると、1秒ごとに4バイトを解放せずにmallocすると、メモリが不足する前にほぼ73時間実行できます。多くのマシンが1日に1度停止することを考慮すると、プログラムがメモリ不足になることはありません!
上記の例では、リークは1秒あたり4バイト、つまり240バイト/分です。ここで、そのバイト/分比を下げると想像してください。その比率が低いほど、プログラムが問題なく実行できる時間が長くなります。 malloc
sがまれな場合、それは本当の可能性です。
ちなみに、malloc
に1回だけアクセスすることがわかっていて、そのmalloc
が再びヒットすることはない場合、静的な割り当てに似ていますが、知る必要はありませんあなたが前もって割り当てているもののサイズ。例:再び512 MBがあるとします。 malloc
32個の整数配列が必要です。これらは典型的な整数で、それぞれ4バイトです。これらの配列のサイズは1024整数を決して超えないことがわかっています。プログラムでは、他のメモリ割り当ては発生しません。十分なメモリがありますか? 32 * 1024 * 4 = 131,072 128 KB-はい。十分なスペースがあります。これ以上メモリを割り当てないことがわかっている場合は、それらの配列を解放せずに安全にmalloc
できます。ただし、これは、プログラムがクラッシュした場合にマシン/デバイスを再起動する必要があることも意味します。プログラムを4,096回開始/停止すると、すべての512 MBが割り当てられます。ゾンビプロセスがある場合、クラッシュした後でもメモリが解放されない可能性があります。
痛みと悲惨さを省き、このマントラをThe One Truthとして使いましょう:malloc
should alwaysはfree
と関連付けられます。 new
はalwaysにdelete
が必要です。
誰も引用していないことに驚いています The Book まだ:
これは最終的には真実ではないかもしれません。メモリが十分に大きくなり、コンピュータの寿命中に空きメモリが不足する可能性があるためです。たとえば、約3×1013 1年に1マイクロ秒なので、1マイクロ秒に1回コンスする場合、約10が必要になります。15 メモリを使い果たすことなく30年間動作できるマシンを構築するためのメモリのセル。そのようなメモリは、今日の基準ではとてつもなく大きいように見えますが、物理的に不可能ではありません。一方、プロセッサは高速化しており、将来のコンピューターでは単一のメモリ上で多数のプロセッサが並行して動作する可能性があるため、想定よりもはるかに高速にメモリを使用できる可能性があります。
http://sarabander.github.io/sicp/html/5_002e3.xhtml#FOOT298
したがって、実際、多くのプログラムは、メモリを解放することなく、問題なく実行できます。
あなたの教授は重要なポイントを上げています。残念ながら、英語の使用法は、彼らが何と言っているのか、私には絶対にわかりません。特定のメモリ使用特性を持ち、個人的に作業したことがある、おもちゃではないプログラムに関して質問に答えさせてください。
一部のプログラムは適切に動作します。彼らはメモリを波のように割り当てます:繰り返しのサイクルで、多くの小さなまたは中規模の割り当てに続いて多くの空きが続きます。これらのプログラムでは、典型的なメモリアロケーターがかなりうまく機能します。それらは解放されたブロックを合体させ、波の終わりには、ほとんどの空きメモリが大きな連続したチャンクになります。これらのプログラムは非常にまれです。
ほとんどのプログラムは動作が悪い。非常に小さいサイズから非常に大きいサイズまでのさまざまなサイズで、メモリを多少ランダムに割り当ておよび割り当て解除し、割り当てられたブロックの高い使用率を保持します。これらのプログラムでは、ブロックを結合する機能は制限されており、時間が経つにつれて、メモリは非常に断片化され、比較的不連続になります。 32ビットのメモリ空間で合計メモリ使用量が約1.5GBを超え、(たとえば)10MB以上の割り当てがある場合、最終的に大きな割り当ての1つが失敗します。これらのプログラムは一般的です。
他のプログラムは、停止するまでメモリをほとんどまたはまったく解放しません。実行中にメモリを徐々に割り当て、少量のみを解放してから停止します。その時点ですべてのメモリが解放されます。コンパイラはこのようなものです。 VMも同様です。たとえば、C++で記述された.NET CLRランタイムは、おそらくメモリを解放しません。なぜそれが必要ですか?
そして、それが最終的な答えです。プログラムのメモリ使用量が十分に大きい場合、mallocとfreeを使用してメモリを管理するだけでは、問題に対する十分な答えにはなりません。適切に動作するプログラムを処理できるほど幸運でない限り、大きなメモリチャンクを事前に割り当ててから、選択した戦略に従ってサブ割り当てを行う1つ以上のカスタムメモリアロケータを設計する必要があります。プログラムが停止する場合を除き、無料で使用することはできません。
あなたの教授が言ったことを正確に知らなければ、真に生産規模のプログラムのために、おそらく彼らの側に出てくるでしょう。
批判のいくつかに答えてみましょう。明らかにSOはこの種の投稿に適した場所ではありません。明確にするために:コンパイラーを含むこの種のソフトウェアを書いた経験は約30年あります。 、私自身のあざだけです。私は、批判がはるかに狭くて短い経験を持つ人々から来るのを感じずにはいられません。
重要なメッセージを繰り返します。mallocとfreeのバランスはnotです。これは、実際のプログラムでの大規模なメモリ割り当てに対する十分なソリューションです。ブロックの合体は正常であり、時間を費やしますが、notで十分です。深刻で賢いメモリアロケータが必要です。メモリアロケータは(mallocなどを使用して)チャンクでメモリを取得し、めったに解放しません。これはおそらくOPの教授が念頭に置いていたメッセージであり、彼はそれを誤解していた。
明示的にメモリを解放する場合の1つのケースについては、役に立たないより悪いです。つまり、最後まですべてのデータが必要プロセスの有効期間。言い換えると、それらを解放するときは、プログラムの終了直前にのみ可能です。最新のOSはプログラムの終了時にメモリを解放するため、free()
を呼び出す必要はありません。実際、メモリ内の複数のページにアクセスする必要があるため、プログラムの終了が遅くなる場合があります。