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


8105

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

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


12
また(本当に)良い:codeproject.com/Articles/76153/…(スタック/ヒープ部分)
Ben


3
関連、スタッククラッシュを参照してください。スタッククラッシュの修正は、システム変数の一部の側面とのような動作に影響を与えましたrlimit_stack。Red Hat Issue 1463241
jww

3
@mattshaneスタックとヒープの定義は、値と参照型にまったく依存しません。つまり、値と参照の型が存在しなかった場合でも、スタックとヒープを完全に定義できます。さらに、値と参照タイプを理解する場合、スタックは実装の詳細にすぎません。Eric Lippert氏:スタックは実装の詳細、パート1です。
マシュー

回答:


5966

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

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

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

質問に直接回答するには:

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

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

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

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

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

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

何が速くなるのですか?

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

明確なデモ:
画像ソース:vikashazrati.wordpress.com


74
良い答えですが、プロセスの開始時にOSによってスタックが割り当てられている間(OSの存在を想定)、プログラムによってインラインで維持されることを追加する必要があると思います。これは、スタックがより高速であるもう1つの理由です。プッシュ操作とポップ操作は通常1つのマシン命令であり、最新のマシンは1サイクルでそれらのうち少なくとも3つを実行できますが、ヒープの割り当てまたは解放にはOSコードの呼び出しが含まれます。
sqykly 2013年

276
最後の図に本当に戸惑っています。その画像を見るまでは手に入れたと思いました。
シーナマダニ

10
@Anarelleプロセッサは、OSの有無にかかわらず命令を実行します。私の心に近い例は、今日知っているようにAPI呼び出しもOSもないSNESですが、スタックがありました。スタックへの割り当ては、これらのシステムでの加算と減算であり、変数を作成した関数から戻ることによって変数がポップされたときに破棄された変数では問題ありませんが、たとえば、コンストラクターと比較すると、結果は単純にはなりません捨てる。そのためには、呼び出しと戻りに関連付けられていないヒープが必要です。ほとんどのOSにはAPIがあり、自分でそれを行う理由はありません
sqykly

2
「スタックは、スクラッチスペースとして確保されるメモリです」。涼しい。しかし、Javaメモリ構造の観点から、実際にどこに「取り除かれる」のでしょうか?ヒープメモリ/非ヒープメモリ/その他(betsol.com/2017/06/…による Javaメモリ構造 )
Jatin Shashoo

4
@JatinShashoo Javaランタイムは、バイトコードインタープリターとして、さらに1レベルの仮想化を追加するので、あなたが言及したのは単なるJavaアプリケーションの観点です。オペレーティングシステムの観点から見ると、これは単なるヒープであり、Javaランタイムプロセスは処理されたバイトコード用の「非ヒープ」メモリとしてその領域の一部を割り当てます。そのOSレベルのヒープの残りは、オブジェクトのデータが格納されるアプリケーションレベルのヒープとして使用されます。
kbec 2018

2350

スタック:

  • ヒープと同じようにコンピュータの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;

31
ポインターpBufferとbの値はスタック上にあり、ほとんどの場合、関数の入り口に割り当てられます。コンパイラによっては、関数の入り口にもバッファが割り当てられる場合があります。
アンディ

36
C言語は、C99言語標準(open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdfで入手可能)で定義されているように、「スタック」が必要であるというのはよくある誤解です。実際、「スタック」という言葉は規格にさえ出てこない。これは、wrt / to Cのスタックの使用法に関するステートメントに一般的に当てはまりますが、言語によって要求されることはありません。詳細についてはknosof.co.uk/cbook/cbook.htmlを参照してください。特にCen.wikipedia.org
wiki / Burroughs_large_systems

55
@Brian buffer []とpBufferポインタがスタック上に作成される理由と、pBufferのデータがヒープ上に作成される理由を説明する必要があります。私はいくつかのpplがあなたの答えで混乱するかもしれないと彼らがプログラムがスタック対ヒープにメモリを割り当てるように特に指示していると思うかもしれないと思うかもしれませんが、これはそうではありません。これは、Bufferが値型であるのに対し、pBufferは参照型だからです
Howiecamp 2010

9
@Remover:ポインターはアドレスを保持せず、ヒープまたはスタック上の何かを等しく指すことができます。new、malloc、およびmallocに類似した他のいくつかの関数はヒープに割り当て、割り当てられたメモリのアドレスを返します。なぜヒープに割り当てたいのですか?あなたの記憶が範囲外に出て、あなたが望むまで解放されないように。
ブライアンR.ボンディ

35
「メモリリークの原因」-ヒープはメモリリークの原因ではありません。がらくたを与えない怠惰な/忘れっぽい/ ex-javaコーダー/コーダーは!
Laz

1370

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

  • アイテムのスタックでは、アイテムはそこに配置された順に他のアイテムの上に重ねられ、削除できるのは(全体を転倒させることなく)一番上のアイテムのみです。

    紙のスタックのようにスタック

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

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

    甘草allsortsのヒープのようなヒープ

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

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

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

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

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

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

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

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

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

    ヒープは、動的に割り当てることができるものの総称です。あなたがそれを見る方法に応じて、それは常にサイズを変えています。現代のプロセッサとオペレーティングシステムでは、それが機能する正確な方法はとにかく非常に抽象化されているので、通常は、それがどのように機能するかについてそれほど心配する必要はありません。まだ割り当てていないか、解放したメモリ。

  • 何が速くなるのですか?

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


20
Davidいいイメージだとか、「プッシュダウンスタック」がコンセプトを説明するのにいい言葉だとか、私は同意しません。スタックに何かを追加しても、スタックの他のコンテンツは押し下げられ、元の場所に残ります。
thomasrutter

8
この答えには大きな間違いが含まれています。静的変数はスタックに割り当てられません。詳細については、私の回答[リンク] stackoverflow.com/a/13326916/1763801を参照してください。あなたは「自動」変数を「静的」変数と同等と
見なし

13
具体的には、「静的に割り当てられたローカル変数」がスタックに割り当てられているとします。実際には、データセグメントに割り当てられます。スタックに割り当てられるのは、自動的に割り当てられる変数(ローカル変数のほとんどではなく、参照ではなく値で渡される関数パラメーターなど)だけです。
davec

9
私はあなたが正しいと気づきました。Cでは、静的割り当て動的ではないものを表す用語というよりは、それ自体が別個のものです。回答を編集しました。ありがとう。
thomasrutter

5
それはCだけではありません。Java、Pascal、Python、その他多くのすべてに、静的割り当て、自動割り当て、動的割り当ての概念があります。「静的割り当て」とは、ほぼすべての場所で同じことを意味します。どの言語でも、静的割り当ては「動的ではない」という意味ではありません。記述しているもの(つまり、スタック上のもの)に「自動」割り当てという用語が必要です。
davec

727

(私はこの回答を多かれ少なかれこの問題のだまされた別の質問から移動しました。)

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

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

ヒープ

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

ヒープ

スタック

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

スタック

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

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

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

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


35
@Martin-より抽象的な受け入れられた回答よりも非常に良い回答/説明。vis関数呼び出しに対して使用されているスタックポインタ/レジスタを示すサンプルアセンブリプログラムは、よりわかりやすくなります。
Bikal Lem

3
すべての参照型は値型(int、stringなど)の構成です。言われているように、値型は、それらが参照型の一部である場合の動作方法よりもスタックに格納されます。
Nps 2014

15
この答えは私の意見では最高でした。なぜなら、それはreturnステートメントが本当に何であるか、そしてそれが時々遭遇するこの「戻りアドレス」とどのように関連するか、関数をスタックにプッシュすることの意味を理解するのに役立ちました、そしてなぜ関数はスタックにプッシュされます。正解です。
Alex

3
これはつまり、ヒープ/スタックがあることを言及するために、私の意見では最高である非常に実装固有の。他の回答は、言語と環境/ OSに関する多くのことを前提としています。+1
Qix-モニカは2014年

2
「関数のコードは、現在のスタックポインターからスタックを上に移動して、これらの値を見つけることができます。」?これについて詳しく教えてもらえますか?
Koray Tugay

404

次のC#コード

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

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

スタック上の変数の画像

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

オブジェクト(更新時にサイズが異なる)はヒープ上に置かれます。これは、作成時にオブジェクトがどれくらい続くかがわからないためです。多くの言語では、ヒープはガベージコレクションによって、参照がなくなったオブジェクト(cls1オブジェクトなど)を見つけます。

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

詳細はここにあります:

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

そしてここ:

スタックとヒープでのオブジェクトの作成

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

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


15
これは誤りです。iとclsは「静的」変数ではありません。それらは「ローカル」または「自動」変数と呼ばれます。それは非常に重要な違いです。[link] stackoverflow.com/a/13326916/1763801 for
clarification

9
それらが静的変数であるとは言いませんでした。私はintとcls1は静的なアイテムだと言った。それらのメモリは静的に割り当てられるため、スタックに格納されます。これは、動的メモリ割り当てを必要とするオブジェクトとは対照的であり、そのためヒープ上で行われます。
Snowcrash、2012年

12
「静的アイテム...スタックに行く」を引用します。これは完全に間違っています。静的アイテムはデータセグメントに入れられ、自動アイテムはスタックに入れられます。
davec

14
また、そのcodeprojectの記事を書いた人は誰も、彼が何について話しているのかわかりません。たとえば、彼は「原始的なものは静的型メモリを必要とする」と言いますが、それは完全に真実ではありません。ヒープ内のプリミティブを動的に割り当てることを妨げるものは何もありません。「int array [] = new int [num]」のようなものと、.NETで動的に割り当てられたプリミティブを書きます。これは、いくつかの不正確さの1つにすぎません。
davec

8
スタックとヒープの内容について深刻な技術的ミスを犯したため、投稿を編集しました。
Tom Leys

209

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

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

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

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

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

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

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


16
ヒープの使用を避けるための推奨事項はかなり強力です。現代のシステムには優れたヒープマネージャーがあり、現代の動的言語は(プログラマが本当に心配することなく)ヒープを幅広く使用しています。ヒープを使用すると思いますが、手動アロケーターを使用して、解放することを忘れないでください!
グレッグ・ヒューギル

2
スタックまたはヒープを使用できる場合は、スタックを使用します。スタックを使用できない場合、本当に選択の余地はありません。私は両方を多く使用し、もちろんstd :: vectorまたは類似のヒットをヒープに使用します。初心者の場合、スタックはとても簡単なので、ヒープを避けます。
トム・レイズ

言語がガベージコレクションを実装していない場合、スマートポインター(動的に割り当てられたメモリのチャンクの参照カウントを行うポインターをラップする個別に割り当てられたオブジェクト)はガベージコレクションと密接に関連しており、安全にヒープを管理する適切な方法ですそして漏れのない方法。これらはさまざまなフレームワークに実装されていますが、独自のプログラムに実装するのもそれほど難しくありません。
BenPen 2016年

1
「これがヒープを回避すべき理由です(まだ頻繁に使用されていますが)。」特にメモリが多くの高級言語で異なる方法で管理されているため、これが実際に何を意味するのかわかりません。この質問には言語にとらわれないタグが付けられているため、この特定のコメント/行は不適切な位置にあり、適用できません。
LintfordPickle

2
良い点@JonnoHampson-あなたが正当な点を述べている間、GCで「高水準言語」で作業しているなら、メモリ割り当てメカニズムはおそらくまったく気にしないと私は主張します。スタックとヒープが何であるかさえ気にします。
トム・Leys

194

明確にするために、この回答には間違った情報が含まれています(トーマスはコメントの後で彼の回答を修正しました、クール:))。他の答えは、静的割り当てが何を意味するかを説明することを避けます。したがって、以下の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

関数内で宣言された静的変数をローカルアクセシビリティのみを持つものとして参照しますが、一般的に「スコープ」という用語は使用しません。また、言語の柔軟性が本質的にゼロである1つのスタック/ヒープの側面:スタックに実行コンテキストを保存する言語は、同じスタックを使用して、作成されたコンテキストよりも長く存続する必要があるものを保持できないことに注意してください。 。のような一部の言語にPostScriptは複数のスタックがありますが、スタックのように動作する「ヒープ」があります。
スーパーキャット2013

@supercatそれはすべて理にかなっています。スコープを「コードのどの部分が変数にアクセスできる」と定義したので(これが最も標準的な定義であると感じます)、私たちは同意すると思います:)
davec

変数の「スコープ」は、時間だけでなく空間によっても制限されていると考えます。クラスオブジェクトスコープの変数は、オブジェクトが存在する限り、その値を保持する必要があります。実行コンテキストスコープ内の変数は、実行がそのコンテキストに留まっている限り、その値を保持する必要があります。静的変数宣言は、スコープが現在のブロックにバインドされている識別子を作成します。現在のブロックは、スコープがバインドされていない変数にアタッチされます
スーパーキャット2013

@supercatこれが、私がライフタイムという言葉を使う理由です。これが、私があなたが時間スコープと呼ぶものを私がどのように呼ぶかです。「スコープ」という単語に非常に多くの意味を詰め込む必要性を減らします。私の知る限りでは、標準的な情報源の間でさえ、正確な定義については完全に一致しているようには見えません。私の専門用語は、一部はK&Rから、一部は私が調査/教えた最初のCS部門での一般的な使用法に基づいています。別の情報に基づいた見解を聞くのはいつも良い。
davec 2013

1
冗談でしょう。あなたは本当に関数内に静的変数を定義できますか?
Zaeem Sattar

168

他のものは広いストロークにかなりよく答えたので、私はいくつかの詳細を投げます。

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

「allocとは対照的に」について:「mallocとは対照的に」という意味ですか?
Peter Mortensen 2017

どのようにポータブルallocaですか?
Peter Mortensen 2017

@PeterMortensen POSIXではないため、移植性は保証されません。
Don Neufeld 2017年

135

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

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

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


117

スタックとは何ですか?

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

ここに画像の説明を入力してください

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

ヒープとは何ですか?

ヒープは、無計画に積み上げられた、整頓されたものの集まりです。

ここに画像の説明を入力してください

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

一緒に

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

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

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

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

Javaメモリモデル

ここに画像の説明を入力してください

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


115

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

ヒープは、オペレーティングシステムによってアプリケーションに与えられるメモリの一部であり、通常はmallocのようなsyscallを介して行われます。最近のOSでは、このメモリは呼び出しプロセスのみがアクセスできるページのセットです。

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

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


2
また、インテルがスタックアクセスを大幅に最適化すること、特に関数からどこに戻るかを予測することなどについても言及する価値があります。
トムレイ

113

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

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


8
別のひっくり返る-ほとんどの答えは(軽く)、「スタック」の使用がC言語によって要求されることを意味します。これは一般的な誤解ですが、実装するための(はるかに)支配的なパラダイムですC99 6.2.4 automatic storage duration objects(変数)(。実際、「スタック」という言葉はC99言語標準にも表示されません。open
std.org

[@Heath]あなたの答えについて少しコメントがあります。この質問に対する受け入れられた回答を見てください。それは言う自由ストアは おそらく同じであるヒープは限らないものの、。
OmarOthman

91

スタックを使用すると、興味深いことができます。たとえば、メモリのヒープではなくスタックを具体的に使用するmallocの形式であるalloca(その使用に関する大量の警告を回避できると想定)のような関数があります。

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


どのようにポータブルallocaですか?たとえば、Windowsで動作しますか?Unixライクなオペレーティングシステム専用ですか?
Peter Mortensen 2017

89

単純に、スタックはローカル変数が作成される場所です。また、サブルーチンを呼び出すたびに、プログラムカウンター(次の機械語命令へのポインター)とすべての重要なレジスター、そして場合によってはパラメーターがスタックにプッシュされます。次に、サブルーチン内のローカル変数がスタックにプッシュされます(そこから使用されます)。サブルーチンが終了すると、それらすべてがスタックからポップされます。PCとレジスターのデータは、ポップされたときと同じように取得および戻されるので、プログラムを陽気に進めることができます。

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

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


[@TED]なぜ「時々パラメータがスタックにプッシュされる」と言ったのですか?私が知っていることは、彼らはいつもそうであるということです。もう少し詳しく説明してもらえますか?
OmarOthman

1
@OmarOthman-私が言うには、サブルーチンが呼び出されたときに何が起こるかはコンパイラ/インタプリタの作成者次第だからです。古典的なFortranの動作は、スタックをまったく使用しないことです。一部の言語では、名前による受け渡しなどのエキゾチックなものがサポートされています。
TED

83

WikiAnwserから。

スタック

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

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

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

ヒープ

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


「受け入れられた答えは、より低いレベルであるため、私はそのほうが好きです。」それは悪いことであり、良いことではありません。
オービットでの軽さのレース2017年

54

スタック

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

ヒープ

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

50

わかりました、簡単に、そして短い言葉で言えば、それらは注文されたものであり、注文されいないことを意味します...!

スタック:スタックアイテムでは、物事は互いの上に配置されます。つまり、処理がより高速で効率的になります!...

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

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

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

ここに画像の説明を入力してください


49

要するに

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


詳細に

スタック

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

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

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


ヒープ

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

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

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

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

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


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

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

ここに画像の説明を入力してください

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

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

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

ここに画像の説明を入力してください

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

さらに、ここここで詳細を説明します


あなたの質問の答えにます

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

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

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

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

すでに上位にあります。

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

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

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

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

何が速くなるのですか?

スタックの割り当ては、スタックポインタを移動するだけなので、はるかに高速です。メモリプールを使用すると、ヒープの割り当てから同等のパフォーマンスを得ることができますが、それにはわずかに複雑さが追加され、独自の頭痛が伴います。

また、スタックとヒープはパフォーマンスを考慮するだけではありません。また、オブジェクトの予想される寿命についても多くの情報を提供します。

詳細はこちらから。


36

1980年代に、UNIXは大企業が独自に事業を展開するうさぎのように繁殖しました。エクソンには、歴史に負けた数十のブランド名と同様に1つありました。メモリのレイアウトは、多くの実装者の裁量によるものでした。

典型的なCプログラムは、brk()値を変更することによって増加する機会を伴って、メモリ内でフラットにレイアウトされました。通常、HEAPはこのbrk値のすぐ下にあり、brkを増やすと使用可能なヒープの量が増加しました。

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

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

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

典型的な1980年代スタイルのUNIX Cプログラムのメモリレイアウト



26

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

これは、起こっていることをより簡単に理解するための簡素化を備えたプロセスメモリ構築の私のビジョンです


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

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

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


23

いくつかの答えがつまらないものになったので、私はダニを寄付します。

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

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

いずれの場合も、ファイバー、グリーンスレッド、コルーチンの両方の目的は、単一のOSレベルのスレッド内で並行してではなく並列に実行される複数の機能(区別についてはこのSOの質問を参照)を持ち、相互に制御を相互に転送することです組織的に。

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

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

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


19

主要な点はすでにカバーされていますが、私は何か共有することがあります。

スタック

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

ヒープ

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

興味深いメモ:

  • 関数呼び出しがヒープに格納されていると、2つの厄介なポイントが発生します。
    1. スタック内の順次ストレージにより、実行はより高速です。ヒープ内のストレージは膨大な時間を消費するため、プログラム全体の実行が遅くなります。
    2. 関数がヒープ(ポインターが指す乱雑なストレージ)に格納されていた場合、呼び出し元のアドレスに戻る方法はありません(メモリー内の順次ストレージにより、スタックはこのアドレスを返します)。

1
簡潔できれい。nice :)
ingconti

13

うわー!非常に多くの答えがあり、そのうちの1つが正しく答えたとは思いません...

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

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

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

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

プライベートヒープが大きくなりすぎると、スタック領域がオーバーラップし、スタックが大きくなりすぎると、スタックがヒープとオーバーラップします。スタックは上位のアドレスから始まり、下位のアドレスに向かって下がっていくため、適切なハッキングを行うと、スタックを非常に大きくして、プライベートヒープ領域がオーバーランし、コード領域がオーバーラップする可能性があります。トリックは、コードにフックできる十分なコード領域をオーバーラップすることです。これは少しトリッキーであり、プログラムがクラッシュする危険がありますが、簡単で非常に効果的です。

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

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

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

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

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

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

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

2d)何が速くなるのですか?

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

REF:

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


8

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

スタックでは、リターンアドレスを保存し、コール→プッシュ/レット→ポップをハードウェアで直接管理します。

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

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

スタックの使用は次のように速くなります:

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

OPPとは OOP(オブジェクト指向プログラミング)を意味しますか?
Peter Mortensen 2017

それmallocがカーネル呼び出しだと言うつもりですか?
Peter Mortensen 2017

1)はい、申し訳ありません。OOP... 2)malloc:まもなく書き込み、申し訳ありません... mallocはユーザー空間にありますが、他の呼び出しをトリガーできます...重要なのは、ヒープの使用が非常に遅いということです。 ...
ingconti 2017

多くの答えは概念として正しいですが、サブルーチン(アセンブリ言語での呼び出し)を呼び出すには、ハードウェア(マイクロプロセッサなど)がスタックを必要とすることに注意してください。」CPUスタック(最新のCPUに1つあった場合)と言語ランタイムスタック(スレッドごとに1つ)を混同しています。プログラマがスタックについて話すとき、これはランタイムのスレッド実行スタックです(例:NETスレッドスタック)。CPUスタックについては話していません。

1

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

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

出典:Academind


0

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

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

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