これは実際に、昨日私が スタックとヒープの両方が必要な理由 について私が尋ねた質問にいくらか関連しています==今日使用します(単純で単一の標準を使用するために、両方ではなく単にヒープを使用できない理由)。
ただし、多くの応答は、割り当てを試行するよりも数百(または数千)倍高速であるため、スタックが置き換え不可能であることを示しました。ヒープを参照します。ヒープを廃止すると動的ストレージ割り当てに問題があることはわかっていますが、これを回避する方法、または動的メモリ割り当てを処理できるようにスタックを改善する方法はないのでしょうか?
スタックの問題は、スタックの一番上になければメモリを「解放」できないことです。たとえば、さまざまなサイズの3つのものを割り当てたとします。
_a = allocate(2000000); // 2000000 bytes
b = allocate(1);
c = allocate(5000000);
_
スタックの下部にはa
、中央にはb
、上部にはc
があります。 b
を解放したい場合、これは問題になります。
_free(b); // b is not on top! We have to wait until c is freed!
_
回避策は、b
の後にすべてのデータを移動し、a
の後に来るようにシフトすることです。これは機能しますが、この場合は50000コピーが必要になります-ヒープよりもかなり遅いものになります。
これが、ヒープがある理由です。割り当てはスタックよりも遅い場合がありますが(O(log n)
vs O(1)
)、ヒープにより、任意の場所のメモリを高速に解放できます-スタックのスタックと比較してO(log n)
O(n)
スタックはスレッドごと、ヒープはプロセス全体
100スレッドすべてがキューに入れたすべての作業項目を処理している場合、100スレッドのいずれかがそれらを見ることができるように、作業項目を正確にどこに割り当てますか?
他の種類のメモリもあります
例えば。メモリマップファイル、共有メモリ、I/Oマップ(カーネルモード)。効率性の議論は、これらの状況では一種のモモです。
スタックはLIFO(last-in-first-out)構造であり、その最上部に参照ポインタが保持されます(通常はハードウェアによってサポートされます)。ヒープの代わりにスタックに割り当てようとすると、このスタックの最上位にあるすべての関数でローカル変数にする必要があります。したがって、スタックに対する主な理由は、main()ルーチンがすべてのデータを事前に割り当てる必要があることです関数呼び出し内で割り当てられたすべてのデータ構造は、それらの関数呼び出しが返されてフレームまたはアクティブ化レコードがポップオフされると最終的に削除されるため、プログラムが使用する構造(プログラムの全期間にわたって存在することを意図)スタック。
スタックは、後入れ先出し(LIFO)ルールに従うメモリ割り当てに最適です。つまり、割り当てた順序とまったく逆の順序でメモリを解放します。 LIFOは非常に一般的で、おそらく最も一般的なメモリ割り当てパターンです。しかし、これは唯一のパターン、または唯一の一般的なパターンではありません。さまざまな問題に取り組むことができる効率的なプログラムを作成するにはより複雑なインフラストラクチャを意味する場合でも、あまり一般的でないパターンを考慮に入れる必要があります。
段落のすべてのメタを取得できる場合:あなたは初心者であり、初心者としては、シンプルさ、そして白黒のルールを重視しています。ただし、初心者には、コンピュータプログラムで対応する必要がある問題と制約の範囲をのぞき見するしかありません。あなたは75年の間活発に開発されてきた技術に入ります。なぜ物事が現状のままであるのかを問うことには何の問題もありませんが、答えは一般的に「ええ、私たちは50年前にシンプルで簡単な方法を試しましたが、クラス全体ではうまく機能しないことがわかりました。問題が発生したため、もっと複雑なことをしなければなりませんでした。」テクノロジーが進歩するにつれ、シンプルさは一般的に効率と柔軟性に道を譲らなければなりません。
別の例として、クロージャ。 popをスタックすると、クロージャーのアクティベーションレコードが失われる可能性があります。したがって、その無名関数とそのデータを永続化したい場合は、ランタイムスタック以外の場所に保存する必要があります。
ヒープストレージを割り当てる理由は他にもたくさんあります。
オブジェクトのアドレスを呼び出し元のプログラムに返したい場合は、スタック変数のアドレスを渡さないでください。スタックストレージが再利用され、呼び出された次の関数によって上書きされる可能性があります。後続の関数の呼び出しによって上書きされないようにするには、malloc()を使用して必要なストレージを取得する必要があります。
スタックアイテムのアドレスを関数から呼び出す関数に渡すことができます。これは、プログラムが "returns()"するまでスタックアイテムが存在することを保証できるためです。ただし、関数が戻るとすぐに、すべてのスタックストレージが取得可能になります。