スタックとヒープはどこにありますか?

8326
mattshane 2008-09-17 18:18.

プログラミング言語の本では、値型はスタック上に作成され、参照型はヒープ上に作成されると説明されていますが、これら2つのことは説明されていません。私はこれについての明確な説明を読んでいません。スタックとは何かを理解しています。だが、

  • それらはどこに何がありますか(物理的には実際のコンピューターのメモリ内)?
  • それらはOSまたは言語ランタイムによってどの程度制御されていますか?
  • それらの範囲は何ですか?
  • それらのそれぞれのサイズを決定するものは何ですか?
  • 何が速くなりますか?

29 answers

6134
Jeff Hill 2008-09-17 18:52.

スタックは、実行スレッドのスクラッチスペースとして確保されるメモリです。関数が呼び出されると、ローカル変数と一部の簿記データ用にスタックの最上位にブロックが予約されます。その関数が戻ると、ブロックは未使用になり、次に関数が呼び出されたときに使用できます。スタックは常にLIFO(後入先出)の順序で予約されます。最後に予約されたブロックは、常に次に解放されるブロックです。これにより、スタックの追跡が非常に簡単になります。スタックからブロックを解放することは、1つのポインターを調整することに他なりません。

ヒープは、動的割り当て用に確保されたメモリです。スタックとは異なり、ヒープからのブロックの割り当てと割り当て解除に強制的なパターンはありません。ブロックはいつでも割り当てて、いつでも解放できます。これにより、ヒープのどの部分がいつでも割り当てられているか、解放されているかを追跡することがはるかに複雑になります。さまざまな使用パターンに合わせてヒープパフォーマンスを調整するために使用できるカスタムヒープアロケーターは多数あります。

各スレッドはスタックを取得しますが、通常、アプリケーションのヒープは1つだけです(ただし、さまざまなタイプの割り当てに対して複数のヒープがあることは珍しくありません)。

質問に直接答えるには:

それらはOSまたは言語ランタイムによってどの程度制御されていますか?

OSは、スレッドの作成時に、システムレベルのスレッドごとにスタックを割り当てます。通常、OSは言語ランタイムによって呼び出され、アプリケーションにヒープを割り当てます。

それらの範囲は何ですか?

スタックはスレッドに接続されているため、スレッドが終了するとスタックが再利用されます。ヒープは通常、ランタイムによるアプリケーションの起動時に割り当てられ、アプリケーション(技術的にはプロセス)が終了すると再利用されます。

それらのそれぞれのサイズを決定するものは何ですか?

スタックのサイズは、スレッドの作成時に設定されます。ヒープのサイズはアプリケーションの起動時に設定されますが、スペースが必要になると大きくなる可能性があります(アロケーターはオペレーティングシステムからより多くのメモリを要求します)。

何が速くなりますか?

アクセスパターンにより、メモリの割り当てと割り当て解除が簡単になるため(ポインタ/整数は単純にインクリメントまたはデクリメントされます)、ヒープには割り当てまたは割り当て解除に関連するはるかに複雑な簿記があります。また、スタック内の各バイトは非常に頻繁に再利用される傾向があります。つまり、プロセッサのキャッシュにマップされる傾向があり、非常に高速になります。ヒープのもう1つのパフォーマンスへの影響は、ほとんどがグローバルリソースであるヒープは、通常、マルチスレッドセーフである必要があることです。つまり、各割り当てと割り当て解除は、プログラム内の他の「すべての」ヒープアクセスと同期する必要があります。

明確なデモンストレーション:
画像ソース:vikashazrati.wordpress.com

2403
Brian R. Bondy 2008-09-17 18:20.

スタック:

  • ヒープと同じようにコンピュータのRAMに保存されます。
  • スタック上に作成された変数はスコープ外になり、自動的に割り当てが解除されます。
  • ヒープ上の変数と比較して、割り当てるのがはるかに高速です。
  • 実際のスタックデータ構造で実装されます。
  • パラメータの受け渡しに使用されるローカルデータ、リターンアドレスを格納します。
  • スタックの使用量が多すぎると、スタックオーバーフローが発生する可能性があります(ほとんどの場合、無限または深すぎる再帰、非常に大きな割り当てから)。
  • スタック上に作成されたデータは、ポインターなしで使用できます。
  • コンパイル時までに割り当てる必要のあるデータの量が正確にわかっていて、大きすぎない場合は、スタックを使用します。
  • 通常、プログラムの開始時に最大サイズがすでに決定されています。

ヒープ:

  • スタックと同じようにコンピュータのRAMに保存されます。
  • C ++では、ヒープ上の変数は手動で破棄する必要があり、スコープから外れることはありません。データはで解放されdeletedelete[]またはfree
  • スタック上の変数と比較して、割り当てが遅くなります。
  • プログラムで使用するデータのブロックを割り当てるためにオンデマンドで使用されます。
  • 割り当てと割り当て解除が多い場合、断片化する可能性があります。
  • C ++またはCでは、ヒープ上に作成されたデータはポインターによってポイントされ、newまたはでmallocそれぞれ割り当てられます。
  • 大きすぎるバッファの割り当てが要求された場合、割り当てが失敗する可能性があります。
  • 実行時に必要なデータの量が正確にわからない場合、または大量のデータを割り当てる必要がある場合は、ヒープを使用します。
  • メモリリークの原因です。

例:

int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;
1391
thomasrutter 2009-03-20 04:38.

最も重要な点は、ヒープとスタックはメモリを割り当てる方法の総称であるということです。それらは多くの異なる方法で実装でき、用語は基本的な概念に適用されます。

  • アイテムのスタックでは、アイテムは配置された順序で上下に配置され、一番上のアイテムのみを削除できます(全体を転倒させることはありません)。

    スタックの単純さは、割り当てられたメモリの各セクションのレコードを含むテーブルを維持する必要がないことです。必要な状態情報は、スタックの最後への単一のポインターだけです。割り当てと割り当て解除を行うには、その単一のポインタをインクリメントおよびデクリメントするだけです。注:スタックは、メモリのセクションの先頭から開始し、上向きではなく下向きに拡張するように実装できる場合があります。

  • ヒープでは、アイテムの配置方法に特定の順序はありません。明確な「トップ」アイテムがないため、任意の順序でアイテムにアクセスして削除できます。

    ヒープの割り当てには、割り当てられているメモリと割り当てられていないメモリの完全な記録を維持する必要があります。また、断片化を減らし、要求されたサイズに収まる大きさの連続したメモリセグメントを見つけるためのオーバーヘッドの維持も必要です。空き領域を残して、いつでもメモリの割り当てを解除できます。メモリアロケータは、割り当てられたメモリを移動してメモリをデフラグしたり、ガベージコレクションを実行したりするなどのメンテナンスタスクを実行する場合があります。

これらのイメージは、スタックとヒープでメモリを割り当てたり解放したりする2つの方法を説明するのにかなり良い仕事をするはずです。ヤム!

  • それらはOSまたは言語ランタイムによってどの程度制御されていますか?

    前述のように、ヒープとスタックは一般的な用語であり、さまざまな方法で実装できます。コンピュータプログラムには通常、呼び出しスタックと呼ばれるスタックがあります。このスタックには、呼び出された関数へのポインタやローカル変数など、現在の関数に関連する情報が格納されます。関数は他の関数を呼び出してから戻るため、スタックは拡大および縮小して、呼び出しスタックのさらに下の関数からの情報を保持します。プログラムは実際にはそれを実行時に制御することはできません。それはプログラミング言語、OS、さらにはシステムアーキテクチャによって決定されます。

    ヒープは、動的かつランダムに割り当てられるメモリに使用される一般的な用語です。つまり、故障しています。通常、メモリはOSによって割り当てられ、アプリケーションはAPI関数を呼び出してこの割り当てを行います。動的に割り当てられたメモリの管理にはかなりのオーバーヘッドが必要です。これは通常、使用されるプログラミング言語または環境のランタイムコードによって処理されます。

  • それらの範囲は何ですか?

    コールスタックは非常に低レベルの概念であるため、プログラミングの意味で「スコープ」とは関係ありません。一部のコードを逆アセンブルすると、スタックの一部への相対ポインタースタイルの参照が表示されますが、高級言語に関する限り、言語には独自のスコープ規則があります。ただし、スタックの重要な側面の1つは、関数が返されると、その関数にローカルなものはすべてスタックからすぐに解放されることです。これは、プログラミング言語がどのように機能するかを考えると、期待どおりに機能します。ヒープ内では、定義することも困難です。スコープはOSによって公開されるものですが、プログラミング言語はおそらく、アプリケーション内の「スコープ」についてのルールを追加します。プロセッサアーキテクチャとOSは仮想アドレス指定を使用します。仮想アドレス指定は、プロセッサが物理アドレスに変換し、ページフォールトなどが発生します。これらは、どのページがどのアプリケーションに属しているかを追跡します。ただし、プログラミング言語がメモリの割り当てと解放に使用する方法を使用し、エラーをチェックするだけなので(何らかの理由で割り当て/解放が失敗した場合)、これについて心配する必要はありません。

  • それらのそれぞれのサイズを決定するものは何ですか?

    繰り返しますが、言語、コンパイラ、オペレーティングシステム、およびアーキテクチャによって異なります。スタックは、定義上、連続したメモリでなければならないため、通常は事前に割り当てられています。言語コンパイラまたはOSがそのサイズを決定します。スタックに大量のデータを格納しないため、不要な無限再帰(つまり、「スタックオーバーフロー」)やその他の異常なプログラミング決定の場合を除いて、完全に使用されないように十分な大きさになります。

    ヒープは、動的に割り当てることができるものすべての総称です。どちらの見方をするかによって、サイズは常に変化しています。最近のプロセッサとオペレーティングシステムでは、動作の正確な方法はとにかく非常に抽象化されているため、(それが可能な言語では)メモリを使用してはならないことを除いて、通常、それがどのように動作するかについてあまり心配する必要はありません。まだ割り当てていないか、解放したメモリ。

  • 何が速くなりますか?

    すべての空きメモリが常に連続しているため、スタックは高速です。空きメモリのすべてのセグメントのリストを維持する必要はありません。スタックの現在の最上位への単一のポインタだけです。コンパイラは通常、この目的のためにこのポインタを特別な高速レジスタに格納します。さらに、スタックでの後続の操作は通常、メモリの非常に近くの領域に集中します。これは、非常に低いレベルでは、プロセッサのオンダイキャッシュによる最適化に適しています。

739
Martin Liversage 2009-08-01 05:54.

(私はこの回答を、多かれ少なかれこれと重複した別の質問から移動しました。)

あなたの質問に対する答えは実装固有であり、コンパイラやプロセッサアーキテクチャによって異なる場合があります。ただし、ここでは簡単に説明します。

  • スタックとヒープはどちらも、基盤となるオペレーティングシステムから割り当てられたメモリ領域です(多くの場合、仮想メモリはオンデマンドで物理メモリにマップされます)。
  • マルチスレッド環境では、各スレッドは独自の完全に独立したスタックを持ちますが、ヒープを共有します。同時アクセスはヒープ上で制御する必要があり、スタック上では不可能です。

ヒープ

  • ヒープには、使用済みブロックと空きブロックのリンクリストが含まれています。ヒープ上の新しい割り当て(newまたはによるmalloc)は、空きブロックの1つから適切なブロックを作成することで満たされます。これには、ヒープ上のブロックのリストを更新する必要があります。ヒープ上のブロックに関するこのメタ情報は、多くの場合、すべてのブロックの直前の小さな領域のヒープにも格納されます。
  • ヒープが大きくなると、新しいブロックが低いアドレスから高いアドレスに割り当てられることがよくあります。したがって、ヒープは、メモリが割り当てられるにつれてサイズが大きくなるメモリブロックのヒープと考えることができます。ヒープが割り当てに対して小さすぎる場合は、基盤となるオペレーティングシステムからより多くのメモリを取得することで、サイズを大きくすることができます。
  • 多くの小さなブロックの割り当てと割り当て解除により、使用済みブロックの間に小さな空きブロックが多数散在している状態でヒープが残る場合があります。大きなブロックの合計サイズが十分に大きい場合でも、割り当て要求を満たすのに十分な大きさの空きブロックがないため、大きなブロックを割り当てる要求が失敗する場合があります。これはヒープフラグメンテーションと呼ばれます
  • フリーブロックに隣接する使用済みブロックの割り当てが解除されると、新しいフリーブロックが隣接するフリーブロックとマージされて、より大きなフリーブロックが作成され、ヒープの断片化が効果的に減少します。

スタック

  • スタックは、多くの場合、スタックポインタという名前のCPU上の特殊レジスタと密接に連携して機能します。最初、スタックポインタはスタックの最上位(スタックの最上位アドレス)を指します。
  • CPUには、値をスタックにプッシュし、スタックからポップバックするための特別な命令があります。プッシュするたびに、スタックポインターの現在の場所に値が格納され、スタックポインターが減少します。ポップ取り出し値は、スタック・ポインタによって指さし、次いで(という事実によって混乱されていないスタックポインタを増加させる追加スタックに値が減少スタックポインタ及び除去値の増加スタックに成長することに注意してください、それを。ボトム)。保存および取得される値は、CPUレジスタの値です。
  • 関数が呼び出されると、CPUは、現在の命令ポインター、つまりスタックで実行されているコードのアドレスをプッシュする特別な命令を使用します。次に、CPUは、呼び出された関数のアドレスに命令ポインタを設定することにより、関数にジャンプします。その後、関数が戻ると、古い命令ポインターがスタックからポップされ、関数の呼び出し直後のコードで実行が再開されます。
  • 関数が入力されると、スタックポインターが減少して、ローカル(自動)変数用にスタック上により多くのスペースが割り当てられます。関数に1つのローカル32ビット変数がある場合、4バイトがスタックに確保されます。関数が戻ると、スタックポインタが戻され、割り当てられた領域が解放されます。
  • 関数にパラメーターがある場合、それらは関数を呼び出す前にスタックにプッシュされます。関数内のコードは、現在のスタックポインターからスタックを上に移動して、これらの値を見つけることができます。
  • 入れ子関数の呼び出しは、魅力のように機能します。新しい呼び出しごとに、関数パラメーター、ローカル変数の戻りアドレスとスペースが割り当てられます。これらのアクティブ化レコードは、ネストされた呼び出し用にスタックでき、関数が戻ったときに正しい方法で巻き戻されます。
  • スタックは限られたメモリブロックであるため、ネストされた関数を呼び出しすぎたり、ローカル変数に割り当てすぎたりすると、スタックオーバーフローが発生する可能性があります。多くの場合、スタックに使用されるメモリ領域は、スタックの最下部(最下位アドレス)の下に書き込むと、CPUでトラップまたは例外がトリガーされるように設定されます。この例外的な状態は、ランタイムによってキャッチされ、ある種のスタックオーバーフロー例外に変換される可能性があります。

関数をスタックではなくヒープに割り当てることはできますか?

いいえ、関数(つまり、ローカル変数または自動変数)のアクティブ化レコードは、これらの変数を格納するだけでなく、ネストされた関数呼び出しを追跡するためにも使用されるスタックに割り当てられます。

ヒープの管理方法は、実際にはランタイム環境によって異なります。CはmallocC ++を使用しますnewが、他の多くの言語にはガベージコレクションがあります。

ただし、スタックは、プロセッサアーキテクチャに密接に関連するより低レベルの機能です。十分なスペースがないときにヒープを拡張することは、ヒープを処理するライブラリ呼び出しで実装できるため、それほど難しくありません。ただし、スタックオーバーフローは手遅れの場合にのみ検出されるため、スタックを増やすことはしばしば不可能です。実行スレッドをシャットダウンすることが唯一の実行可能なオプションです。

410
Snowcrash 2012-11-10 02:28.

次のC#コードでは

public void Method1()
{
    int i = 4;
    int y = 2;
    class1 cls1 = new class1();
}

メモリの管理方法は次のとおりです

Local Variablesこれは、関数の呼び出しがスタック内にある限り続く必要があります。ヒープは、その存続期間が前もって実際にはわからない変数に使用されますが、それらはしばらく続くと予想されます。ほとんどの言語では、変数をスタックに格納する場合、コンパイル時に変数の大きさを知ることが重要です。

オブジェクト(更新するとサイズが異なります)は、作成時にオブジェクトがどのくらいの期間続くかわからないため、ヒープに移動します。多くの言語では、ヒープはガベージコレクションされて、参照がなくなったオブジェクト(cls1オブジェクトなど)を検索します。

Javaでは、ほとんどのオブジェクトが直接ヒープに入ります。C / C ++のような言語では、ポインターを処理していないときに、構造体とクラスがスタックに残ることがよくあります。

詳細については、こちらをご覧ください。

スタックとヒープのメモリ割り当ての違い«timmurphy.org

そしてここ:

スタックとヒープにオブジェクトを作成する

この記事は上の図のソースです:6つの重要な.NET概念:スタック、ヒープ、値型、参照型、ボックス化、およびボックス化解除-CodeProject

ただし、いくつかの不正確さが含まれている可能性があることに注意してください。

212
Tom Leys 2008-09-17 18:27.

スタック関数を呼び出すと、その関数への引数とその他のオーバーヘッドがスタックに置かれます。いくつかの情報(帰りにどこに行くかなど)もそこに保存されます。関数内で変数を宣言すると、その変数もスタックに割り当てられます。

スタックの割り当て解除は、常に割り当てとは逆の順序で割り当て解除するため、非常に簡単です。関数に入るとスタックのものが追加され、関数を終了すると対応するデータが削除されます。これは、他の多くの関数を呼び出す(または再帰的なソリューションを作成する)多くの関数を呼び出さない限り、スタックの小さな領域内にとどまる傾向があることを意味します。

ヒープザ・ヒープは、あなたがその場で作成したデータを置く場所の総称です。プログラムが作成する宇宙船の数がわからない場合は、新しい(またはmallocまたは同等の)演算子を使用して各宇宙船を作成する可能性があります。この割り当てはしばらく続くため、作成した順序とは異なる順序で解放される可能性があります。

したがって、ヒープははるかに複雑になります。これは、未使用のメモリ領域がチャンクとインターリーブされてしまうためです。メモリは断片化されます。必要なサイズの空きメモリを見つけるのは難しい問題です。これが、ヒープを回避する必要がある理由です(ただし、まだ頻繁に使用されています)。

実装スタックとヒープの両方の実装は、通常、ランタイム/ OSに依存します。多くの場合、パフォーマンスが重要なゲームやその他のアプリケーションは、ヒープから大量のメモリを取得し、それを内部でディッシュして、メモリをOSに依存しないようにする独自のメモリソリューションを作成します。

これは、メモリ使用量が標準とはかなり異なる場合にのみ実用的です。つまり、ある巨大な操作でレベルをロードし、別の巨大な操作で全体をチャックできるゲームの場合です。

メモリ内の物理的な場所これは、仮想メモリと呼ばれるテクノロジにより、物理データが別の場所(ハードディスク上でも!)にある特定のアドレスにアクセスできるとプログラムに思わせるため、思ったほど重要ではありません。スタック用に取得するアドレスは、呼び出しツリーが深くなるにつれて昇順です。ヒープのアドレスは予測不可能であり(つまり、実装固有)、率直に言って重要ではありません。

199
davec 2012-11-11 13:03.

明確にするために、この回答には誤った情報が含まれています(トーマスはコメントの後に彼の回答を修正しました、かっこいい:))。他の答えは、静的割り当ての意味を説明することを避けています。そこで、割り当ての3つの主要な形式と、それらが通常、ヒープ、スタック、およびデータセグメントにどのように関連するかを以下で説明します。また、人々が理解しやすいように、C / C ++とPythonの両方でいくつかの例を示します。

「静的」(別名静的に割り当てられた)変数は、スタックに割り当てられません。そうは思わないでください。多くの人は、「静的」が「スタック」によく似ているという理由だけでそうします。それらは実際にはスタックにもヒープにも存在しません。これらは、データセグメントと呼ばれるものの一部です。

ただし、一般的には、「スタック」と「ヒープ」よりも「スコープ」と「ライフタイム」を考慮する方が適切です。

スコープとは、コードのどの部分が変数にアクセスできるかを指します。一般に、ローカルスコープ(現在の関数でのみアクセス可能)とグローバルスコープ(どこからでもアクセス可能)を考えますが、スコープははるかに複雑になる可能性があります。

ライフタイムとは、プログラムの実行中に変数が割り当てられ、割り当てが解除される時期を指します。通常、静的割り当て(変数はプログラムの全期間を通じて持続するため、複数の関数呼び出しにわたって同じ情報を格納するのに役立ちます)と自動割り当て(変数は関数への1回の呼び出し中にのみ持続するため、関数中にのみ使用され、完了したら破棄できる情報の格納)と動的割り当て(静的または自動のようなコンパイル時ではなく、実行時に期間が定義される変数)。

ほとんどのコンパイラとインタプリタは、スタックやヒープなどの使用に関して同様にこの動作を実装しますが、動作が正しい限り、コンパイラは必要に応じてこれらの規則に違反することがあります。たとえば、最適化により、ほとんどのローカル変数がスタックに存在する場合でも、ローカル変数はレジスタにのみ存在するか、完全に削除される可能性があります。いくつかのコメントで指摘されているように、スタックやヒープを使用せず、代わりに他のいくつかのストレージメカニズムを使用するコンパイラを自由に実装できます(スタックとヒープはこれに最適であるため、ほとんど実行されません)。

これらすべてを説明するために、いくつかの簡単な注釈付きCコードを提供します。学ぶための最良の方法は、デバッガーの下でプログラムを実行し、動作を監視することです。Pythonを読みたい場合は、答えの最後までスキップしてください:)

// Statically allocated in the data segment when the program/DLL is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in the code
int someGlobalVariable;

// Statically allocated in the data segment when the program is first loaded
// Deallocated when the program/DLL exits
// scope - can be accessed from anywhere in this particular code file
static int someStaticVariable;

// "someArgument" is allocated on the stack each time MyFunction is called
// "someArgument" is deallocated when MyFunction returns
// scope - can be accessed only within MyFunction()
void MyFunction(int someArgument) {

    // Statically allocated in the data segment when the program is first loaded
    // Deallocated when the program/DLL exits
    // scope - can be accessed only within MyFunction()
    static int someLocalStaticVariable;

    // Allocated on the stack each time MyFunction is called
    // Deallocated when MyFunction returns
    // scope - can be accessed only within MyFunction()
    int someLocalVariable;

    // A *pointer* is allocated on the stack each time MyFunction is called
    // This pointer is deallocated when MyFunction returns
    // scope - the pointer can be accessed only within MyFunction()
    int* someDynamicVariable;

    // This line causes space for an integer to be allocated in the heap
    // when this line is executed. Note this is not at the beginning of
    // the call to MyFunction(), like the automatic variables
    // scope - only code within MyFunction() can access this space
    // *through this particular variable*.
    // However, if you pass the address somewhere else, that code
    // can access it too
    someDynamicVariable = new int;


    // This line deallocates the space for the integer in the heap.
    // If we did not write it, the memory would be "leaked".
    // Note a fundamental difference between the stack and heap
    // the heap must be managed. The stack is managed for us.
    delete someDynamicVariable;

    // In other cases, instead of deallocating this heap space you
    // might store the address somewhere more permanent to use later.
    // Some languages even take care of deallocation for you... but
    // always it needs to be taken care of at runtime by some mechanism.

    // When the function returns, someArgument, someLocalVariable
    // and the pointer someDynamicVariable are deallocated.
    // The space pointed to by someDynamicVariable was already
    // deallocated prior to returning.
    return;
}

// Note that someGlobalVariable, someStaticVariable and
// someLocalStaticVariable continue to exist, and are not
// deallocated until the program exits.

ライフタイムとスコープを区別することが重要である理由の特に痛烈な例は、変数がローカルスコープを持つことができるが静的ライフタイムを持つことができることです-たとえば、上記のコードサンプルの「someLocalStaticVariable」。このような変数は、私たちの一般的ではあるが非公式な命名習慣を非常に混乱させる可能性があります。たとえば、「ローカル」と言うときは通常「ローカルスコープの自動的に割り当てられた変数」を意味し、グローバルと言うときは通常「グローバルスコープの静的に割り当てられた変数」を意味します。残念ながら、それは「のようなものになると、ファイルは静的に割り当てられた変数のスコープ多くの人々はちょうど言う...」「ハァッを???」。

C / C ++での構文の選択のいくつかは、この問題を悪化させます。たとえば、多くの人々は、以下に示す構文のために、グローバル変数は「静的」ではないと考えています。

int var1; // Has global scope and static allocation
static int var2; // Has file scope and static allocation

int main() {return 0;}

上記の宣言にキーワード「static」を入れると、var2がグローバルスコープを持つことができなくなることに注意してください。それにもかかわらず、グローバルvar1には静的な割り当てがあります。これは直感的ではありません!このため、スコープを説明するときに「静的」という言葉を使用せず、代わりに「ファイル」や「ファイル制限」スコープなどを使用するようにしています。ただし、多くの人は「静的」または「静的スコープ」というフレーズを使用して、1つのコードファイルからのみアクセスできる変数を説明します。ライフタイムのコンテキストでは、「静的」とは常に、変数がプログラムの開始時に割り当てられ、プログラムの終了時に割り当てが解除されることを意味します。

一部の人々は、これらの概念をC / C ++固有のものと考えています。ではない。たとえば、以下のPythonサンプルは、3つのタイプの割り当てすべてを示しています(ここでは説明しませんが、インタープリター言語で可能な微妙な違いがいくつかあります)。

from datetime import datetime

class Animal:
    _FavoriteFood = 'Undefined' # _FavoriteFood is statically allocated

    def PetAnimal(self):
        curTime = datetime.time(datetime.now()) # curTime is automatically allocatedion
        print("Thank you for petting me. But it's " + str(curTime) + ", you should feed me. My favorite food is " + self._FavoriteFood)

class Cat(Animal):
    _FavoriteFood = 'tuna' # Note since we override, Cat class has its own statically allocated _FavoriteFood variable, different from Animal's

class Dog(Animal):
    _FavoriteFood = 'steak' # Likewise, the Dog class gets its own static variable. Important to note - this one static variable is shared among all instances of Dog, hence it is not dynamic!


if __name__ == "__main__":
    whiskers = Cat() # Dynamically allocated
    fido = Dog() # Dynamically allocated
    rinTinTin = Dog() # Dynamically allocated

    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

    Dog._FavoriteFood = 'milkbones'
    whiskers.PetAnimal()
    fido.PetAnimal()
    rinTinTin.PetAnimal()

# Output is:
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is steak
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is tuna
# Thank you for petting me. But it's 13:05:02.255000, you should feed me. My favorite food is milkbones
# Thank you for petting me. But it's 13:05:02.256000, you should feed me. My favorite food is milkbones
171
Don Neufeld 2008-09-17 18:48.

他の人は幅広いストロークにかなりよく答えているので、いくつか詳細を紹介します。

  1. スタックとヒープは単一である必要はありません。複数のスタックがある一般的な状況は、プロセスに複数のスレッドがある場合です。この場合、各スレッドには独自のスタックがあります。複数のヒープを持つこともできます。たとえば、一部のDLL構成では、異なるヒープから異なるDLLが割り当てられる可能性があるため、異なるライブラリによって割り当てられたメモリを解放することは一般的にお勧めできません。

  2. Cでは、ヒープに割り当てるallocとは対照的に、スタックに割り当てるallocaを使用することで、可変長割り当ての利点を得ることができます。このメモリはreturnステートメントに耐えられませんが、スクラッチバッファには役立ちます。

  3. あまり使用しないWindowsで巨大な一時バッファを作成するのは無料ではありません。これは、コンパイラがスタックが存在することを確認するために関数が入力されるたびに呼び出されるスタックプローブループを生成するためです(Windowsはスタックの最後にある単一のガードページを使用して、スタックを拡張する必要があるときを検出するためです。スタックの最後から1ページ以上離れたメモリにアクセスすると、クラッシュします)。例:

void myfunction()
{
   char big[10000000];
   // Do something that only uses for first 1K of big 99% of the time.
}
138
bk1e 2008-09-17 21:16.

他の人があなたの質問に直接答えましたが、スタックとヒープを理解しようとするときは、従来のUNIXプロセス(スレッドとmmap()ベースのアロケーターなし)のメモリレイアウトを検討することが役立つと思います。メモリ管理用語集のWebページには、このメモリレイアウトの図があります。

スタックとヒープは、従来、プロセスの仮想アドレス空間の両端に配置されていました。スタックは、アクセスされると、カーネルによって設定されたサイズ(で調整可能)まで自動的に大きくなりますsetrlimit(RLIMIT_STACK, ...)。ヒープは、メモリアロケータがbrk()またはsbrk()システムコールを呼び出すと大きくなり、物理メモリのより多くのページをプロセスの仮想アドレス空間にマッピングします。

一部の組み込みシステムなど、仮想メモリのないシステムでは、スタックとヒープのサイズが固定されていることを除いて、同じ基本レイアウトが適用されることがよくあります。ただし、他の組み込みシステム(Microchip PICマイクロコントローラーに基づくシステムなど)では、プログラムスタックは、データ移動命令ではアドレス指定できない別個のメモリブロックであり、プログラムフロー命令を介して間接的にのみ変更または読み取ることができます(呼び出し、返品など)。Intel Itaniumプロセッサなどの他のアーキテクチャには、複数のスタックがあります。この意味で、スタックはCPUアーキテクチャの要素です。

119
Daniel Papasian 2008-09-17 18:29.

スタックはメモリの一部であり、「pop」(スタックから値を削除して返す)や「push」(スタックに値をプッシュする)など、いくつかの主要なアセンブリ言語命令を介して操作できますが、(サブルーチンを呼び出す-これはアドレスをプッシュしてスタックに戻す)そして戻る(サブルーチンから戻る-これはアドレスをスタックからポップしてそれにジャンプする)。これは、スタックポインタレジスタの下のメモリ領域であり、必要に応じて設定できます。スタックは、サブルーチンに引数を渡すため、およびサブルーチンを呼び出す前にレジスタの値を保持するためにも使用されます。

ヒープは、オペレーティングシステムによって、通常はmallocのようなシステムコールを介してアプリケーションに提供されるメモリの一部です。最新のOSでは、このメモリは呼び出しプロセスのみがアクセスできるページのセットです。

スタックのサイズは実行時に決定され、通常、プログラムの起動後に大きくなることはありません。Cプログラムでは、スタックは、各関数内で宣言されたすべての変数を保持するのに十分な大きさである必要があります。ヒープは必要に応じて動的に拡張されますが、OSは最終的に呼び出しを行います(多くの場合、ヒープはmallocが要求する値よりも大きくなるため、少なくとも一部の将来のmallocはカーネルに戻る必要がありません。より多くのメモリを取得します。この動作は多くの場合カスタマイズ可能です)

プログラムを起動する前にスタックを割り当てているので、スタックを使用する前にmallocを実行する必要はありません。これは、わずかな利点です。実際には、仮想メモリサブシステムを備えた最新のオペレーティングシステムでは、ページの実装方法と保存場所が実装の詳細であるため、何が高速で何が低速になるかを予測することは非常に困難です。

118
Shreyos Adikari 2014-06-12 09:42.

スタックとは何ですか?

スタックはオブジェクトの山であり、通常はきちんと配置されています。

コンピューティングアーキテクチャのスタックは、データが後入れ先出し方式で追加または削除されるメモリ領域です。
マルチスレッドアプリケーションでは、各スレッドに独自のスタックがあります。

ヒープとは何ですか?

ヒープは、無計画に積み上げられたものの乱雑なコレクションです。

コンピューティングアーキテクチャでは、ヒープは動的に割り当てられたメモリの領域であり、オペレーティングシステムまたはメモリマネージャライブラリによって自動的に管理されます。
ヒープ上のメモリは、プログラムの実行中に定期的に割り当て、割り当て解除、およびサイズ変更されます。これにより、フラグメンテーションと呼ばれる問題が発生する可能性があります。
断片化は、メモリオブジェクトが、追加のメモリオブジェクトを保持するには小さすぎる小さなスペースを間に置いて割り当てられた場合に発生します。
最終的な結果は、それ以上のメモリ割り当てに使用できないヒープスペースのパーセンテージです。

両方一緒に

マルチスレッドアプリケーションでは、各スレッドに独自のスタックがあります。ただし、すべての異なるスレッドがヒープを共有します。
異なるスレッドはマルチスレッドアプリケーションでヒープを共有するため、これは、スレッドがヒープ内の同じメモリにアクセスして操作しようとしないように、スレッド間に何らかの調整が必要であることも意味します。同時に。

スタックとヒープのどちらが速いですか?なぜ?

スタックはヒープよりもはるかに高速です。
これは、メモリがスタックに割り当てられる方法によるものです。
スタックへのメモリの割り当ては、スタックポインタを上に移動するのと同じくらい簡単です。

プログラミングに不慣れな人にとっては、スタックの方が簡単なので、おそらくスタックを使用することをお勧めします。
スタックは小さいため、データに必要なメモリの量が正確にわかっている場合、またはデータのサイズが非常に小さいことがわかっている場合に使用することをお勧めします。
データに大量のメモリが必要であることがわかっている場合、または必要なメモリ量がわからない場合(動的配列の場合など)は、ヒープを使用することをお勧めします。

Javaメモリモデル

スタックは、ローカル変数(メソッドパラメーターを含む)が格納されるメモリの領域です。オブジェクト変数に関して言えば、これらはヒープ上の実際のオブジェクトへの単なる参照(ポインター)です。
オブジェクトがインスタンス化されるたびに、ヒープメモリのチャンクがそのオブジェクトのデータ(状態)を保持するために確保されます。オブジェクトには他のオブジェクトを含めることができるため、このデータの一部は実際にはそれらのネストされたオブジェクトへの参照を保持できます。

116
Noname 2008-09-17 18:57.

他の多くの人がこの問題についてあなたにほとんど正しい答えを与えたと思います。

ただし、見逃されている詳細の1つは、「ヒープ」は実際にはおそらく「フリーストア」と呼ばれるべきであるということです。この区別の理由は、元のフリーストアが「二項ヒープ」と呼ばれるデータ構造で実装されていたためです。そのため、malloc()/ free()の初期の実装からの割り当ては、ヒープからの割り当てでした。ただし、この現代では、ほとんどの無料ストアは、二項ヒープではない非常に複雑なデータ構造で実装されています。

91
Peter 2009-03-20 05:55.

あなたはスタックでいくつかの面白いことをすることができます。たとえば、alloca(その使用に関する大量の警告を乗り越えることができると仮定)のような関数があります。これは、メモリにヒープではなくスタックを特別に使用するmallocの形式です。

とはいえ、スタックベースのメモリエラーは私が経験した中で最悪のもののいくつかです。ヒープメモリを使用していて、割り当てられたブロックの境界を超えた場合、セグメント違反を引き起こす可能性が十分にあります。(100%ではありません:ブロックが以前に割り当てた別のブロックと偶然に隣接している可能性があります。)ただし、スタックに作成された変数は常に互いに隣接しているため、範囲外に書き込むと別の変数の値が変わる可能性があります。私のプログラムが論理の法則に従わなくなったと感じるときはいつでも、それはおそらくバッファオーバーフローであることを学びました。

89
T.E.D. 2009-03-20 05:13.

簡単に言うと、スタックはローカル変数が作成される場所です。また、サブルーチンを呼び出すたびに、プログラムカウンタ(次のマシン命令へのポインタ)と重要なレジスタがあり、パラメータがスタックにプッシュされることもあります。次に、サブルーチン内のローカル変数がスタックにプッシュされます(そしてそこから使用されます)。サブルーチンが終了すると、そのすべてがスタックからポップバックされます。PCとレジスタのデータは、ポップされたときの状態で取得および戻されるため、プログラムは順調に進むことができます。

ヒープは、動的メモリ割り当てが行われるメモリの領域です(明示的な「新規」または「割り当て」呼び出し)。これは、さまざまなサイズのメモリブロックとその割り当てステータスを追跡できる特別なデータ構造です。

「クラシック」システムでは、RAMは、スタックポインタがメモリの下部から始まり、ヒープポインタが上部から始まり、互いに向かって大きくなるように配置されていました。それらが重複している場合は、RAMが不足しています。ただし、これは最新のマルチスレッドOSでは機能しません。すべてのスレッドには独自のスタックが必要であり、それらは動的に作成できます。

82
devXen 2009-04-02 15:25.

WikiAnwserから。

スタック

関数またはメソッドが別の関数を呼び出し、次に別の関数を呼び出すなどの場合、最後の関数がその値を返すまで、それらすべての関数の実行は中断されたままになります。

スタック内の要素(関数呼び出し)は相互に依存しているため、この中断された関数呼び出しのチェーンはスタックです。

スタックは、例外処理とスレッド実行で考慮することが重要です。

ヒープ

ヒープは、プログラムが変数を格納するために使用するメモリです。ヒープの要素(変数)は相互に依存関係がなく、いつでもランダムにアクセスできます。

55
unknown 2014-01-30 20:33.

スタック

  • 非常に高速なアクセス
  • 変数の割り当てを明示的に解除する必要はありません
  • スペースはCPUによって効率的に管理され、メモリは断片化されません
  • ローカル変数のみ
  • スタックサイズの制限(OSに依存)
  • 変数のサイズを変更することはできません

ヒープ

  • 変数にはグローバルにアクセスできます
  • メモリサイズに制限はありません
  • (比較的)遅いアクセス
  • スペースの効率的な使用は保証されていません。メモリのブロックが割り当てられてから解放されると、時間の経過とともにメモリが断片化する可能性があります。
  • メモリを管理する必要があります(変数の割り当てと解放を担当します)
  • realloc()を使用して変数のサイズを変更できます
51
Abrar Jahin 2016-05-03 02:16.

要するに

スタックは静的メモリ割り当てに使用され、ヒープは動的メモリ割り当てに使用され、どちらもコンピュータのRAMに格納されます。


詳細に

スタック

スタックは「LIFO」(後入れ先出し)データ構造であり、CPUによって非常に厳密に管理および最適化されます。関数が新しい変数を宣言するたびに、その変数はスタックに「プッシュ」されます。その後、関数が終了するたびに、その関数によってスタックにプッシュされたすべての変数が解放されます(つまり、それらは削除されます)。スタック変数が解放されると、そのメモリ領域は他のスタック変数で使用できるようになります。

スタックを使用して変数を格納することの利点は、メモリが管理されることです。手動でメモリを割り当てたり、不要になったメモリを解放したりする必要はありません。さらに、CPUはスタックメモリを非常に効率的に編成するため、スタック変数の読み取りと書き込みは非常に高速です。

詳細はこちらをご覧ください


ヒープ

ヒープは、コンピュータのメモリの領域であり、自動的に管理されることはなく、CPUによって厳密に管理されることもありません。それはメモリのより自由に動く領域です(そしてより大きくなります)。ヒープにメモリを割り当てるには、組み込みのC関数であるmalloc()またはcalloc()を使用する必要があります。ヒープにメモリを割り当てたら、free()を使用して、メモリが不要になったらそのメモリの割り当てを解除する必要があります。

これを怠ると、プログラムにメモリリークと呼ばれるものが発生します。つまり、ヒープ上のメモリは引き続き確保されます(そして、他のプロセスで使用できなくなります)。デバッグのセクションで説明するように、メモリリークの検出に役立つValgrindというツールがあります。

スタックとは異なり、ヒープには可変サイズのサイズ制限がありません(コンピューターの明らかな物理的制限は別として)。ヒープメモリは、ヒープ上のメモリにアクセスするためにポインタを使用する必要があるため、読み取りと書き込みが少し遅くなります。ポインタについては後ほど説明します。

スタックとは異なり、ヒープ上に作成された変数には、プログラム内の任意の関数からアクセスできます。ヒープ変数のスコープは基本的にグローバルです。

詳細はこちらをご覧ください


スタックに割り当てられた変数はメモリに直接格納され、このメモリへのアクセスは非常に高速であり、その割り当てはプログラムのコンパイル時に処理されます。関数またはメソッドが別の関数を呼び出し、次に別の関数を呼び出すなどの場合、最後の関数がその値を返すまで、それらすべての関数の実行は中断されたままになります。スタックは常にLIFOの順序で予約され、最後に予約されたブロックは常に次に解放されるブロックです。これにより、スタックの追跡が非常に簡単になります。スタックからブロックを解放することは、1つのポインターを調整するだけです。

ヒープに割り当てられた変数には実行時にメモリが割り当てられ、このメモリへのアクセスは少し遅くなりますが、ヒープサイズは仮想メモリのサイズによってのみ制限されます。ヒープの要素は相互に依存関係がなく、いつでもランダムにアクセスできます。ブロックはいつでも割り当てて、いつでも解放できます。これにより、ヒープのどの部分がいつでも割り当てられているか、解放されているかを追跡することがはるかに複雑になります。

コンパイル時までに割り当てる必要のあるデータの量が正確にわかっていて、大きすぎない場合は、スタックを使用できます。実行時に必要なデータの量が正確にわからない場合、または大量のデータを割り当てる必要がある場合は、ヒープを使用できます。

マルチスレッドの状況では、各スレッドは独自の完全に独立したスタックを持ちますが、ヒープを共有します。スタックはスレッド固有であり、ヒープはアプリケーション固有です。スタックは、例外処理とスレッド実行で考慮することが重要です。

各スレッドはスタックを取得しますが、通常、アプリケーションのヒープは1つだけです(ただし、さまざまなタイプの割り当てに対して複数のヒープがあることは珍しくありません)。

実行時に、アプリケーションがより多くのヒープを必要とする場合は、空きメモリからメモリを割り当てることができ、スタックがメモリを必要とする場合は、アプリケーションに割り当てられた空きメモリからメモリを割り当てることができます。

さらに、ここここに詳細が示さています


あなたの質問の答えに来てください

それらはOSまたは言語ランタイムによってどの程度制御されていますか?

OSは、スレッドの作成時に、システムレベルのスレッドごとにスタックを割り当てます。通常、OSは言語ランタイムによって呼び出され、アプリケーションにヒープを割り当てます。

詳細はこちらをご覧ください

それらの範囲は何ですか?

すでに上に与えられています。

「コンパイル時までに割り当てる必要のあるデータの量が正確にわかっていて、大きすぎない場合は、スタックを使用できます。実行時に必要なデータの量が正確にわからない場合、または次の場合は、ヒープを使用できます。大量のデータを割り当てる必要があります。」

詳細については、こちらをご覧ください。

それらのそれぞれのサイズを決定するものは何ですか?

スタックのサイズは、スレッドの作成時にOSによって設定されます。ヒープのサイズはアプリケーションの起動時に設定されますが、スペースが必要になると大きくなる可能性があります(アロケーターはオペレーティングシステムからより多くのメモリを要求します)。

何が速くなりますか?

実際に行うのはスタックポインタを移動することだけなので、スタック割り当てははるかに高速です。メモリプールを使用すると、ヒープ割り当てから同等のパフォーマンスを得ることができますが、それには少し複雑さが増し、それ自体が頭痛の種になります。

また、スタックとヒープはパフォーマンスの考慮事項だけではありません。また、オブジェクトの予想寿命について多くのことを教えてくれます。

詳細はこちらからご覧いただけます

50
Alireza 2017-07-19 05:04.

OK、簡単に言えば、それらは注文されたもの注文されいないものを意味します...!

スタック:スタックアイテムでは、物事が互いに重なり合うため、処理がより速く、より効率的になります!...

したがって、特定のアイテムを指すインデックスが常にあり、処理も高速になります。アイテム間の関係もあります!...

ヒープ:順序がなく、処理が遅くなり、値が特定の順序やインデックスなしで台無しになります...ランダムであり、それらの間に関係はありません...したがって、実行時間と使用時間は異なる可能性があります...

また、以下の画像を作成して、どのように見えるかを示します。

38
Yousha Aleayoub 2017-09-15 07:32.

仮想メモリ内の各プロセスのスタックヒープ、およびデータ

36
jlettvin 2015-03-28 09:55.

1980年代、UNIXは大企業が独自に転がり、うさぎのように広まりました。エクソンには、歴史に失われた数十のブランド名と同様に1つありました。メモリがどのようにレイアウトされたかは、多くの実装者の裁量でした。

典型的なCプログラムは、brk()値を変更することで増加する機会があり、メモリ内にフラットに配置されました。通常、HEAPはこのbrk値をわずかに下回り、brkを増やすと、使用可能なヒープの量が増えました。

単一のスタックは通常、HEAPの下の領域であり、メモリの次の固定ブロックの先頭まで何も価値のないメモリの領域でした。この次のブロックは、その時代の有名なハッキングの1つでスタックデータによって上書きされる可能性のあるCODEであることがよくありました。

1つの典型的なメモリブロックはBSS(ゼロ値のブロック)でしたが、あるメーカーの製品では誤ってゼロ化されませんでした。もう1つは、文字列や数値などの初期化された値を含むDATAでした。3つ目は、CRT(Cランタイム)、メイン、関数、およびライブラリを含むCODEでした。

UNIXでの仮想メモリの出現により、多くの制約が変わりました。これらのブロックが連続している、サイズが固定されている、または現在特定の方法で注文されている必要があるという客観的な理由はありません。もちろん、UNIXが登場する前は、これらの制約に悩まされていなかったMulticsでした。これは、その時代のメモリレイアウトの1つを示す回路図です。

27
Maxim Akristiniy 2015-12-18 05:08.

数セント:メモリをグラフィカルでより単純に描くのが良いと思います:


矢印-成長スタックとヒープ、プロセススタックサイズに制限があり、OSで定義されている場所、通常はスレッド作成APIのパラメーターによるスレッドスタックサイズの制限を示します。ヒープは通常、プロセスの最大仮想メモリサイズによって制限されます(たとえば、32ビット2〜4 GB)。

とても簡単な方法:プロセスヒープはプロセスとその中のすべてのスレッドに一般的であり、malloc()のような一般的なケースでメモリ割り当てに使用されます。

スタックは、一般的な場合に関数の戻りポインタと変数を格納するためのクイックメモリであり、関数呼び出し、ローカル関数変数のパラメータとして処理されます。

23
shakurov 2015-03-02 15:29.

いくつかの答えがつまらなくなったので、私は私のダニに貢献するつもりです。

驚いたことに、複数の(つまり、実行中のOSレベルのスレッドの数とは関係のない)コールスタックが、エキゾチックな言語(PostScript)やプラットフォーム(Intel Itanium)だけでなく、ファイバーグリーンスレッドにも見られるとは誰も言及していません。コルーチンのいくつかの実装。

繊維、グリーンスレッド、コルーチンは多くの点で類似しており、多くの混乱を招きます。ファイバーとグリーンスレッドの違いは、前者は協調マルチタスクを使用するのに対し、後者は協調またはプリエンプティブのいずれか(または両方)を備えている可能性があることです。ファイバーとコルーチンの違いについては、こちらをご覧ください。

いずれにせよ、ファイバー、グリーンスレッド、コルーチンの両方の目的は、複数の関数を同時に実行することですが、単一のOSレベルスレッド内で並行して実行することはなく(区別についてはこのSOの質問を参照)、制御を相互に転送します。組織化された方法で。

ファイバー、グリーンスレッド、またはコルーチンを使用する場合、通常、関数ごとに個別のスタックがあります。(技術的には、スタックだけでなく、実行のコンテキスト全体が関数ごとにあります。最も重要なのは、CPUレジスタです。)すべてのスレッドには、同時に実行されている関数と同じ数のスタックがあり、スレッドは各関数の実行を切り替えます。プログラムのロジックに従って。関数が最後まで実行されると、そのスタックは破棄されます。したがって、スタックの数と存続期間は動的であり、OSレベルのスレッドの数によって決定されるわけはありません。

通常、関数ごとに個別のスタックがある」と言ったことに注意してください。クールルーチンには、スタックフルスタックレスの両方の実装があります。最も注目すべきstackful C ++の実装がありBoost.CoroutineとマイクロソフトPPLさんasync/await。(ただし、C ++ 17に提案されたC ++の再開可能な関数(別名「asyncおよびawait」)は、スタックレスコルーチンを使用する可能性があります。)

C ++標準ライブラリへのファイバーの提案が間もなく開始されます。また、いくつかのサードパーティライブラリがあります。グリーンスレッドは、PythonやRubyなどの言語で非常に人気があります。

20
Pankaj Kumar Thapa 2017-11-16 08:27.

主要なポイントはすでにカバーされていますが、私は共有することがあります。

スタック

  • 非常に高速なアクセス。
  • RAMに保存されます。
  • 関数呼び出しは、渡されたローカル変数と関数パラメーターとともにここにロードされます。
  • プログラムがスコープ外になると、スペースは自動的に解放されます。
  • シーケンシャルメモリに保存されます。

ヒープ

  • スタックに比べてアクセスが遅い。
  • RAMに保存されます。
  • 動的に作成された変数はここに保存され、後で使用後に割り当てられたメモリを解放する必要があります。
  • メモリ割り当てが行われる場所に保存され、常にポインタによってアクセスされます。

興味深いメモ:

  • 関数呼び出しがヒープに格納されていたとしたら、2つの厄介な点が発生していました。
    1. スタックにシーケンシャルストレージがあるため、実行が高速になります。ヒープに格納すると、膨大な時間が消費されるため、プログラム全体の実行速度が低下します。
    2. 関数がヒープ(ポインターが指す乱雑なストレージ)に格納されている場合、呼び出し元のアドレスに戻る方法はありませんでした(メモリ内のシーケンシャルストレージのためにスタックが提供します)。
15
ar18 2019-02-20 16:04.

うわー!非常に多くの答えがあり、そのうちの1つが正しく理解できなかったと思います...

1)それらはどこに何がありますか(物理的には実際のコンピューターのメモリ内)?

スタックは、プログラムイメージに割り当てられた最大のメモリアドレスとして始まり、そこから値が減少するメモリです。これは、呼び出された関数パラメーターおよび関数で使用されるすべての一時変数用に予約されています。

パブリックとプライベートの2つのヒープがあります。

プライベートヒープは、プログラムのコードの最後のバイトの後の16バイト境界(64ビットプログラムの場合)または8バイト境界(32ビットプログラムの場合)で始まり、そこから値が増加します。デフォルトヒープとも呼ばれます。

プライベートヒープが大きくなりすぎると、スタック領域とオーバーラップします。スタックが大きくなりすぎると、スタックがオーバーラップします。スタックは高いアドレスから始まり、低いアドレスに向かって進むため、適切なハッキングを行うと、スタックを非常に大きくして、プライベートヒープ領域をオーバーランさせ、コード領域とオーバーラップさせることができます。その場合の秘訣は、コードにフックできるようにコード領域を十分にオーバーラップさせることです。実行するのは少しトリッキーで、プログラムがクラッシュするリスクがありますが、簡単で非常に効果的です。

パブリックヒープは、プログラムイメージスペースの外側にある独自のメモリスペースにあります。メモリリソースが不足した場合にハードディスクに吸い上げられるのはこのメモリです。

2)OSまたは言語ランタイムによってどの程度制御されていますか?

スタックはプログラマーによって制御され、プライベートヒープはOSによって管理され、パブリックヒープはOSサービスであるため、誰によっても制御されません。要求を行うと、要求が許可または拒否されます。

2b)その範囲は何ですか?

それらはすべてプログラムに対してグローバルですが、その内容はプライベート、パブリック、またはグローバルにすることができます。

2c)それぞれのサイズを決定するものは何ですか?

スタックとプライベートヒープのサイズは、コンパイラのランタイムオプションによって決まります。パブリックヒープは、サイズパラメータを使用して実行時に初期化されます。

2d)何が1つを速くしますか?

それらは高速になるように設計されているのではなく、便利になるように設計されています。プログラマーがそれらをどのように利用するかによって、それらが「速い」か「遅い」かが決まります。

参照:

https://norasandler.com/2019/02/18/Write-a-Compiler-10.html

https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap

https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate

9
ingconti 2017-07-28 12:14.

概念としては多くの答えが正しいですが、サブルーチン(アセンブリ言語ではCALL)を呼び出すには、ハードウェア(つまりマイクロプロセッサ)にスタックが必要であることに注意する必要があります。(OOPの人はそれをメソッドと呼びます

スタックにリターンアドレスを保存し、call→push / ret→popはハードウェアで直接管理されます。

スタックを使用してパラメーターを渡すことができます。レジスターを使用するよりも遅い場合でも(マイクロプロセッサーの第一人者または1980年代の優れたBIOSブック...)

  • スタックがないと、マイクロプロセッサは機能しません。(アセンブリ言語であっても、サブルーチン/関数のないプログラムを想像することはできません)
  • ヒープがなくても可能です。(ヒープはOSの概念であり、mallocのように、OS / Lib呼び出しであるため、アセンブリ言語プログラムは機能しません。

スタックの使用は次のように高速です。

  • ハードウェアであり、プッシュ/ポップでさえ非常に効率的です。
  • mallocでは、カーネルモードに入り、ロック/セマフォ(または他の同期プリミティブ)を使用してコードを実行し、割り当てを追跡するために必要な構造を管理する必要があります。
5
Neeraj Bansal 2020-07-07 01:13.

ヒープは、オペレーティングシステムまたはメモリマネージャライブラリによって自動的に管理される、動的に割り当てられたメモリの領域です。ブロックはいつでも割り当てて、いつでも解放できます。ヒープの割り当てには、割り当てられているメモリと割り当てられていないメモリの完全な記録を維持する必要があります。また、断片化を減らし、要求されたサイズに収まる大きさの連続したメモリセグメントを見つけるためのオーバーヘッドの維持も必要です。空き領域を残して、いつでもメモリの割り当てを解除できます。ヒープが大きくなると、新しいブロックが低いアドレスから高いアドレスに割り当てられることがよくあります。したがって、ヒープは、メモリが割り当てられるにつれてサイズが大きくなるメモリブロックのヒープと考えることができます。ヒープが割り当てに対して小さすぎる場合は、基盤となるオペレーティングシステムからより多くのメモリを取得することで、サイズを大きくすることができます。ヒープから割り当てられたメモリは、次のいずれかが発生するまで割り当てられたままになります。

  • メモリが解放されます
  • プログラムは終了します

スタック

  • ヒープと同じようにコンピュータのRAMに保存されます。
  • スタック上に作成された変数はスコープ外になり、自動的に割り当てが解除されます。
  • ヒープ上の変数と比較して、割り当てるのがはるかに高速です。
  • パラメータの受け渡しに使用されるローカルデータ、リターンアドレスを格納します。
  • スタックの使用量が多すぎると、スタックオーバーフローが発生する可能性があります(ほとんどの場合、無限または深すぎる再帰、非常に大きな割り当てから)。
  • コンパイル時までに割り当てる必要のあるデータの量が正確にわかっていて、大きすぎない場合は、スタックを使用します。
  • 通常、プログラムの開始時に最大サイズがすでに決定されています。

ヒープ:

  • スタックと同じようにコンピュータのRAMに保存されます。
  • C ++では、ヒープ上の変数は手動で破棄する必要があり、スコープから外れることはありません。
  • データは、delete、delete []、またはfreeで解放されます。
  • スタック上の変数と比較して、割り当てが遅くなります。
  • プログラムで使用するデータのブロックを割り当てるためにオンデマンドで使用されます。
  • 割り当てと割り当て解除が多い場合、断片化する可能性があります。
  • C ++またはCでは、ヒープ上に作成されたデータはポインターによってポイントされ、それぞれnewまたはmallocで割り当てられます。
  • 大きすぎるバッファの割り当てが要求された場合、割り当てが失敗する可能性があります。
  • 実行時に必要なデータの量が正確にわからない場合、または大量のデータを割り当てる必要がある場合は、ヒープを使用します。
  • メモリリークの原因です。
2
NattyC 2020-03-22 17:16.

スタックは基本的にアクセスしやすいメモリであり、アイテムを-よく-スタックとして管理するだけです。サイズが事前にわかっているアイテムのみがスタックに入ることができます。これは、数値、文字列、ブール値の場合です。

ヒープを使用すると、正確なサイズと構造を事前できないのアイテム用のメモリです。オブジェクトと配列は実行時に変更および変更される可能性があるため、ヒープに入れる必要があります。

出典:Academind

2
Olivier Rogier 2020-07-06 00:50.

CPUスタックとヒープは、CPUとレジスタがメモリでどのように機能するか、マシンアセンブリ言語がどのように機能するかに物理的に関連しており、高級言語自体ではなく、これらの言語が小さなことを決定できる場合でも関係します。

最新のCPUはすべて、「同じ」マイクロプロセッサ理論で動作します。これらはすべて「レジスタ」と呼ばれるものに基づいており、一部は「スタック」でパフォーマンスを向上させるためのものです。私が知っているように、すべてのCPUには最初からスタックレジスタがあり、それらは常にここにありました。アセンブリ言語は、バリエーションはあるものの、最初から同じです...マイクロソフトとその中間言語(IL)まで、パラダイムをOO仮想マシンアセンブリ言語に変更しました。したがって、将来的にはCLI / CIL CPUを使用できるようになります(MSの1つのプロジェクト)。

CPUには、メモリアクセスを高速化するためのスタックレジスタがありますが、プロセスで使用可能なすべてのメモリへのフルアクセスを取得するために他のレジスタを使用する場合と比較して制限があります。スタックとヒープの割り当てについて話し合ったのはそのためです。

要約すると、一般的に、ヒープはハッジで低速であり、スタックが小さくて高速であり、「ローカル」変数と参照(それらの管理を忘れる隠しポインター)のための「グローバル」インスタンスとオブジェクトコンテンツ用です。

したがって、メソッドでnewキーワードを使用すると、参照(int)がスタックに作成されますが、覚えていれば、オブジェクトとそのすべてのコンテンツ(値型と​​オブジェクト)はヒープに作成されます。ただし、ローカルの基本値型と配列はスタックに作成されます。

メモリアクセスの違いは、セル参照レベルにあります。プロセスの全体的なメモリであるヒープのアドレス指定には、CPUスタックのためにアドレス指定の点でローカルに「多い」スタックよりも、CPUレジスタの処理の点でより複雑なものが必要です。覚えていれば、レジスタはベースアドレスとして使用されます。

非常に長いまたは無限の再帰呼び出しまたはループがある場合、最新のコンピューターでシステムをフリーズすることなく、スタックオーバーフローがすぐに発生するのはそのためです...

.NETでのC#ヒープ(ing)とスタック(ing)

スタックとヒープ:違いを知る

保存される静的クラスのメモリ割り当てC#

スタックとヒープはどこにありますか?

https://en.wikipedia.org/wiki/Memory_management

https://en.wikipedia.org/wiki/Stack_register

アセンブリ言語リソース:

アセンブリプログラミングチュートリアル

インテル®64およびIA-32アーキテクチャーソフトウェア開発者マニュアル

1
aquagremlin 2020-04-10 05:29.

本当に良い議論をありがとう、しかし本当の初心者として、私は指示がどこに保管されているのだろうか?初めに、科学者は2つのアーキテクチャ(すべてがデータと見なされるフォンノイマンと、メモリの領域が命令用に予約されたハーバードとデータ用に予約されたハーバード)のどちらかを決定していました。最終的には、フォンノイマン型の設計を採用しましたが、今ではすべてが「同じ」と見なされています。これは私がアセンブリを学んでいたときに私にとって困難でしたhttps://www.cs.virginia.edu/~evans/cs216/guides/x86.html 彼らはレジスタとスタックポインタについて話しているからです。

上記のすべてがデータについて語っています。私の推測では、命令は特定のメモリフットプリントを持つ定義済みのものであるため、スタックに配置され、アセンブリで説明されているすべての「これらの」レジスタはスタックに配置されます。もちろん、その後、動的な構造に命令とデータが混在するオブジェクト指向プログラミングが登場したので、命令もヒープに保持されるようになりましたか?

Related questions

MORE COOL STUFF

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは、夫に会ったとき、典型的な交際のアドバイスに逆らいました。

マイケルシーンが非営利の俳優である理由

マイケルシーンが非営利の俳優である理由

マイケルシーンは非営利の俳優ですが、それは正確にはどういう意味ですか?

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。

「たどりつけば」をオンラインでストリーミングできない理由

「たどりつけば」をオンラインでストリーミングできない理由

ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

サロンからのヘアトリミングや個人的な寄付は、油流出を吸収して環境を保護するのに役立つマットとして再利用できます。

ホワイトハウスの最も記憶に残る結婚式を見てください

ホワイトハウスの最も記憶に残る結婚式を見てください

過去200年以上の間にホワイトハウスで結婚したのはほんの数人です。彼らは誰でしたか、そしてそこで結婚式を獲得するために何が必要ですか?

ジェイ・ブルースはどうやら子供を妊娠することによってメッツから離れて取引されていることを祝った

ジェイ・ブルースはどうやら子供を妊娠することによってメッツから離れて取引されていることを祝った

あなたが一時的に会っていなかったとき。シーズン11-1を開始したチームであるニューヨークメッツは、日曜日の午後にフィラデルフィアで行われた最後の11試合の9試合目を失いました。

スティーブンキングのアウトサイダーはトランプ時代のそれです

スティーブンキングのアウトサイダーはトランプ時代のそれです

スティーブン・キングのアウトサイダーは、多くの点で先祖返りの小説であり、80年代の全盛期から引き裂かれたように見える生き物の特徴であり、おそらくセル以来の彼の最もパルプのような本ですが、今日の恐怖の中で間違いなく設立された作品です。表面上は、形を変えるペニーワイズのような子供たちの殺人者を中心としており、その最も暗い脅威は、封じ込められず、神経質に平凡なものよりも幻想的で打ち負かされません。

スティーブンユニバースは、強烈な内部エピソードのペアで、それ自体のバックストーリーをさりげなく粉砕します

スティーブンユニバースは、強烈な内部エピソードのペアで、それ自体のバックストーリーをさりげなく粉砕します

スティーブンユニバースビーチシティのエピソードが実行されるたびに、いくつかのクライマックスイベントが発生し、スティーブンユニバースのより広い神話に対する理解の一部が失われます。これはあなたが期待していたことですか?今日のエピソードは両方とも、容赦なくゆっくりと、シーズンの終盤の主要な部分を設定する決定的な結論に向かって進みます。そして、ロナウドは、静かな納屋が倒れているのを発見した夜中にスティーブンを捕まえるためにやって来ます。月に。

ディズニーワールドの旅行のヒントを教えてください

ディズニーワールドの旅行のヒントを教えてください

「光が触れるものはすべて私たちの王国です。」今週のHackYour Cityでは、1つのテーマパークを取り上げます。

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、生後4か月の娘、モナコに母乳育児をしていると語った。

投資ノート:Bioscout AU$300万シード

投資ノート:Bioscout AU$300万シード

Bioscoutは、農家を運転席に置くという使命を負っています。Artesian(GrainInnovate)やUniseedと並んで、最新のシードラウンドでチームを支援できることをうれしく思います。問題真菌症による重大な作物の損失は、農民にとって試練であることが証明されています。

リトルマーケットリサーチ1| 2022年のクイックグリンプス遠隔医療市場

リトルマーケットリサーチ1| 2022年のクイックグリンプス遠隔医療市場

遠隔医療は、パンデミック後の時代では新しいものではなく、時代遅れの分野でもありません。しかし、業界を詳しく見ると、需要と供給の強力な持続可能性と、米国で絶え間ない革命となる強力な潜在的成長曲線を示しています。

スタートアップ資金調達環境:タイのスタートアップエコシステムの次は何ですか?

スタートアップ資金調達環境:タイのスタートアップエコシステムの次は何ですか?

2021年は、世界的なベンチャーキャピタル(VC)の資金調達にとって記録的な年でした。DealStreetAsiaによると、東南アジアも例外ではなく、この地域では年間で記録的な25の新しいユニコーンが採掘されました。

ムーアの法則を超えて

ムーアの法則を超えて

計算に対する私たちの欲求とムーアの法則が提供できるものとの間には、指数関数的に増大するギャップがあります。私たちの文明は計算に基づいています—建築と想像力の現在の限界を超える技術を見つけなければなりません。

Language