21750
4548
プログラミング言語の本では、値型はスタック上に作成され、参照型はヒープ上に作成されると説明されていますが、これら2つのことは説明されていません。私はこれについての明確な説明を読んでいません。スタックとは何かを理解しています。だが、
それらはどこに何がありますか(物理的には実際のコンピューターのメモリ内)?
それらはOSまたは言語のランタイムによってどの程度制御されていますか?
それらの範囲は何ですか?
それらのそれぞれのサイズを決定するものは何ですか?
何が速くなりますか? 
スタックは、実行スレッドのスクラッチスペースとして確保されるメモリです。関数が呼び出されると、ローカル変数と一部の簿記データ用にスタックの最上位にブロックが予約されます。その関数が戻ると、ブロックは未使用になり、次に関数が呼び出されたときに使用できます。スタックは常にLIFO(後入れ先出し)の順序で予約されます。最後に予約されたブロックは、常に次に解放されるブロックです。これにより、スタックの追跡が非常に簡単になります。スタックからブロックを解放することは、1つのポインターを調整することに他なりません。
ヒープは、動的割り当て用に確保されたメモリです。スタックとは異なり、ヒープからのブロックの割り当てと割り当て解除に強制的なパターンはありません。ブロックはいつでも割り当てて、いつでも解放できます。これにより、ヒープのどの部分がいつでも割り当てられているか、空いているかを追跡することがはるかに複雑になります。さまざまな使用パターンに合わせてヒープパフォーマンスを調整するために使用できるカスタムヒープアロケータは多数あります。
各スレッドはスタックを取得しますが、通常、アプリケーションのヒープは1つだけです(ただし、さまざまなタイプの割り当てに対して複数のヒープがあることは珍しくありません)。
質問に直接答えるには:
それらはOSまたは言語ランタイムによってどの程度制御されていますか?
OSは、スレッドの作成時に、システムレベルのスレッドごとにスタックを割り当てます。通常、OSは言語ランタイムによって呼び出され、アプリケーションにヒープを割り当てます。
それらの範囲は何ですか?
スタックはスレッドに接続されているため、スレッドが終了するとスタックが再利用されます。ヒープは通常、ランタイムによるアプリケーションの起動時に割り当てられ、アプリケーション(技術的にはプロセス)が終了すると再利用されます。
それらのそれぞれのサイズを決定するものは何ですか?
スタックのサイズは、スレッドの作成時に設定されます。ヒープのサイズはアプリケーションの起動時に設定されますが、スペースが必要になると大きくなる可能性があります(アロケーターはオペレーティングシステムからより多くのメモリを要求します)。
何が速くなりますか?
アクセスパターンにより、メモリの割り当てと割り当て解除が簡単になるため(ポインタ/整数は単純にインクリメントまたはデクリメントされます)、ヒープには割り当てまたは割り当て解除に関連するはるかに複雑な簿記があります。また、スタック内の各バイトは非常に頻繁に再利用される傾向があります。つまり、プロセッサのキャッシュにマップされる傾向があり、非常に高速になります。ヒープのもう1つのパフォーマンスへの影響は、ほとんどがグローバルリソースであるヒープは、通常、マルチスレッドセーフである必要があることです。つまり、各割り当てと割り当て解除は、プログラム内の他の「すべての」ヒープアクセスと同期する必要があります。
明確なデモンストレーション:
画像ソース:vikashazrati.wordpress.com
|
スタック:
ヒープと同じようにコンピュータのRAMに保存されます。
スタック上に作成された変数はスコープ外になり、自動的に割り当てが解除されます。
ヒープ上の変数と比較して、割り当てるのがはるかに高速です。
実際のスタックデータ構造で実装されます。
パラメータの受け渡しに使用されるローカルデータ、リターンアドレスを格納します。
スタックの使用量が多すぎると、スタックオーバーフローが発生する可能性があります(ほとんどの場合、無限または深すぎる再帰、非常に大きな割り当てによる)。
スタック上に作成されたデータは、ポインターなしで使用できます。
コンパイル時に割り当てる必要のあるデータの量が正確にわかっていて、大きすぎない場合は、スタックを使用します。
通常、最大サイズはプログラムの開始時にすでに決定されています。
ヒープ:
スタックと同じようにコンピュータのRAMに保存されます。
C ++では、ヒープ上の変数は手動で破棄する必要があり、スコープから外れることはありません。データは、delete、delete []、またはfreeで解放されます。
スタック上の変数と比較して、割り当てが遅くなります。
プログラムで使用するデータのブロックを割り当てるためにオンデマンドで使用されます。
割り当てと割り当て解除が多い場合、断片化する可能性があります。
C ++またはCでは、ヒープ上に作成されたデータはポインターによってポイントされ、それぞれnewまたはmallocで割り当てられます。
大きすぎるバッファの割り当てが要求された場合、割り当てが失敗する可能性があります。
実行時に必要なデータの量が正確にわからない場合、または大量のデータを割り当てる必要がある場合は、ヒープを使用します。
メモリリークの原因です。
例:
int foo()
{{
char * pBuffer; // <-まだ何も割り当てられていません(ここでスタックに割り当てられているポインタ自体を除く)。
bool b = true; //スタックに割り当てられます。
if(b)
{{
//スタックに500バイトを作成します
charバッファ[500];
//ヒープに500バイトを作成します
pBuffer = new char [500];
} // <-バッファはここで割り当て解除されますが、pBufferは割り当て解除されません
} // <---おっと、メモリリークがあります。delete[] pBufferを呼び出す必要があります。
|
最も重要な点は、ヒープとスタックはメモリを割り当てる方法の総称であるということです。それらは多くの異なる方法で実装でき、用語は基本的な概念に適用されます。
アイテムのスタックでは、アイテムは配置された順序で上下に配置され、一番上のアイテムのみを削除できます。(全体を転倒させることなく)。
スタックの単純さは、割り当てられたメモリの各セクションのレコードを含むテーブルを維持する必要がないことです。必要な状態情報は、スタックの最後への単一のポインターだけです。割り当てと割り当て解除を行うには、その単一のポインタをインクリメントおよびデクリメントするだけです。注:スタックは、メモリのセクションの先頭から開始し、上向きではなく下向きに拡張するように実装できる場合があります。
ヒープでは、アイテムの配置方法に特定の順序はありません。明確な「トップ」アイテムがないため、任意の順序でアイテムにアクセスして削除できます。
ヒープの割り当てには、割り当てられているメモリと割り当てられていないメモリの完全な記録を維持する必要があります。また、断片化を減らし、要求されたサイズに収まる大きさの連続したメモリセグメントを見つけるためのオーバーヘッドの維持も必要です。空き領域を残して、いつでもメモリの割り当てを解除できます。メモリアロケータは、割り当てられたメモリを移動してメモリをデフラグしたり、ガベージコレクションを実行したりするなどのメンテナンスタスクを実行する場合があります。
これらのイメージは、スタックとヒープでメモリを割り当てたり解放したりする2つの方法を説明するのにかなり良い仕事をするはずです。ヤム!
それらはOSまたは言語ランタイムによってどの程度制御されていますか?
前述のように、ヒープとスタックは一般的な用語であり、さまざまな方法で実装できます。コンピュータプログラムには通常、呼び出しスタックと呼ばれるスタックがあります。このスタックには、呼び出された関数へのポインタやローカル変数など、現在の関数に関連する情報が格納されます。関数は他の関数を呼び出してから戻るため、スタックは拡大および縮小して、呼び出しスタックのさらに下の関数からの情報を保持します。プログラムは実際にはそれを実行時に制御することはできません。それはプログラミング言語、OS、さらにはシステムアーキテクチャによって決定されます。
ヒープは、動的かつランダムに割り当てられるメモリに使用される一般的な用語です。つまり、故障しています。通常、メモリはOSによって割り当てられ、アプリケーションはAPI関数を呼び出してこの割り当てを行います。動的に割り当てられたメモリの管理にはかなりのオーバーヘッドが必要です。これは通常、使用されるプログラミング言語または環境のランタイムコードによって処理されます。
それらの範囲は何ですか?
コールスタックは非常に低レベルの概念であるため、プログラミングの意味で「スコープ」とは関係ありません。一部のコードを逆アセンブルすると、スタックの一部への相対ポインタースタイルの参照が表示されますが、高級言語に関する限り、言語には独自のスコープ規則があります。ただし、スタックの重要な側面の1つは、関数が戻ると、その関数にローカルなものはすべてスタックからすぐに解放されることです。これは、プログラミング言語がどのように機能するかを考えると、期待どおりに機能します。ヒープ内では、定義することも困難です。スコープはOSによって公開されるものですが、プログラミング言語はおそらく、アプリケーション内の「スコープ」についてのルールを追加します。プロセッサアーキテクチャとOSは仮想アドレス指定を使用します。仮想アドレス指定は、プロセッサが物理アドレスに変換し、ページフォールトなどが発生します。これらは、どのページがどのアプリケーションに属しているかを追跡します。ただし、プログラミング言語がメモリの割り当てと解放に使用する方法を使用し、エラーをチェックするだけなので(何らかの理由で割り当て/解放が失敗した場合)、これについて心配する必要はありません。
それらのそれぞれのサイズを決定するものは何ですか?
繰り返しますが、言語、コンパイラ、オペレーティングシステム、およびアーキテクチャによって異なります。スタックは、定義上、連続したメモリでなければならないため、通常は事前に割り当てられています。言語コンパイラまたはOSがそのサイズを決定します。スタックに大量のデータを保存しないので、不要な無限再帰(つまり、「スタックオーバーフロー」)やその他の異常なプログラミング決定の場合を除いて、完全に使用されないように十分な大きさになります。
ヒープは、動的に割り当てることができるものすべての総称です。どちらの見方をするかによって、サイズは常に変化しています。最近のプロセッサとオペレーティングシステムでは、動作の正確な方法はとにかく非常に抽象化されているため、(それが可能な言語では)メモリを使用してはならないことを除いて、通常、それがどのように動作するかについてあまり心配する必要はありません。まだ割り当てていないか、解放したメモリ。
何が速くなりますか?
すべての空きメモリが常に連続しているため、スタックは高速です。空きメモリのすべてのセグメントのリストを維持する必要はありません。スタックの現在の最上位への単一のポインタだけです。コンパイラは通常、この目的のためにこのポインタを特別な高速レジスタに格納します。さらに、スタックでの後続の操作は通常、メモリの非常に近くの領域に集中します。これは、非常に低いレベルでは、プロセッサオンダイによる最適化に適しています。キャッシュ。
|
(私はこの回答を、多かれ少なかれこれと重複した別の質問から移動しました。)
あなたの質問に対する答えは実装固有であり、コンパイラやプロセッサアーキテクチャによって異なる場合があります。ただし、ここでは簡単に説明します。
スタックとヒープはどちらも、基盤となるオペレーティングシステムから割り当てられたメモリ領域です(多くの場合、仮想メモリはオンデマンドで物理メモリにマップされます)。
マルチスレッド環境では、各スレッドは独自の完全に独立したスタックを持ちますが、ヒープを共有します。同時アクセスはヒープ上で制御する必要があり、スタック上では不可能です。
ヒープ
ヒープには、使用済みブロックと空きブロックのリンクリストが含まれています。ヒープ上の新しい割り当て(newまたはmallocによる)は、空きブロックの1つから適切なブロックを作成することで満たされます。これには、ヒープ上のブロックのリストを更新する必要があります。ヒープ上のブロックに関するこのメタ情報は、多くの場合、すべてのブロックの直前の小さな領域のヒープにも格納されます。
ヒープが大きくなると、新しいブロックが低いアドレスから高いアドレスに割り当てられることがよくあります。したがって、ヒープは、メモリが割り当てられるにつれてサイズが大きくなるメモリブロックのヒープと考えることができます。ヒープが割り当てに対して小さすぎる場合は、基盤となるオペレーティングシステムからより多くのメモリを取得することで、サイズを大きくすることができます。
多くの小さなブロックの割り当てと割り当て解除により、使用済みブロックの間に小さな空きブロックが多数散在している状態でヒープが残る場合があります。空きブロックの合計サイズが十分に大きい場合でも、割り当て要求を満たすのに十分な大きさの空きブロックがないため、大きなブロックを割り当てる要求が失敗する場合があります。これはヒープフラグメンテーションと呼ばれます。
フリーブロックに隣接する使用済みブロックの割り当てが解除されると、新しいフリーブロックが隣接するフリーブロックとマージされて、より大きなフリーブロックが作成され、ヒープの断片化が効果的に削減されます。
スタック
スタックは、多くの場合、スタックポインタという名前のCPU上の特殊レジスタと密接に連携して機能します。最初、スタックポインタはスタックの最上位(スタックの最上位アドレス)を指します。
CPUには、値をスタックにプッシュし、スタックからポップバックするための特別な命令があります。プッシュするたびに、スタックポインターの現在の場所に値が格納され、スタックポインターが減少します。ポップは、スタックポインターが指す値を取得してから、スタックポインターを増やします(スタックに値を追加するとスタックポインターが減り、値を削除するとスタックポインターが増えるという事実と混同しないでください。スタックは、ボトム)。保存および取得される値は、CPUレジスタの値です。
関数が呼び出されると、CPUは現在の命令ポインター、つまりスタックで実行されているコードのアドレスをプッシュする特別な命令を使用します。次に、CPUは、を設定することにより、機能にジャンプします。
呼び出された関数のアドレスへの命令ポインタ。その後、関数が戻ると、古い命令ポインターがスタックからポップされ、関数の呼び出し直後のコードで実行が再開されます。
関数が入力されると、スタックポインターが減少して、ローカル(自動)変数用にスタック上により多くのスペースが割り当てられます。関数に1つのローカル32ビット変数がある場合、4バイトがスタックに確保されます。関数が戻ると、スタックポインタが戻され、割り当てられた領域が解放されます。
関数にパラメーターがある場合、それらは関数を呼び出す前にスタックにプッシュされます。関数内のコードは、現在のスタックポインターからスタックを上に移動して、これらの値を見つけることができます。
入れ子関数の呼び出しは、魅力のように機能します。新しい呼び出しごとに、関数パラメーター、ローカル変数の戻りアドレスとスペースが割り当てられます。これらのアクティブ化レコードは、ネストされた呼び出し用にスタックでき、関数が戻るときに正しい方法で巻き戻されます。
スタックは限られたメモリブロックであるため、ネストされた関数を呼び出しすぎたり、ローカル変数に割り当てすぎたりすると、スタックオーバーフローが発生する可能性があります。多くの場合、スタックに使用されるメモリ領域は、スタックの最下部(最下位アドレス)の下に書き込むと、CPUでトラップまたは例外がトリガーされるように設定されます。この例外的な状態は、ランタイムによってキャッチされ、ある種のスタックオーバーフロー例外に変換されます。
関数をスタックではなくヒープに割り当てることはできますか?
いいえ、関数(ローカル変数または自動変数)のアクティブ化レコードは、これらの変数を格納するだけでなく、ネストされた関数呼び出しを追跡するためにも使用されるスタックに割り当てられます。
ヒープの管理方法は、実際にはランタイム環境によって異なります。 Cはmallocを使用し、C ++はnewを使用しますが、他の多くの言語にはガベージコレクションがあります。
ただし、スタックは、プロセッサアーキテクチャに密接に関連するより低レベルの機能です。十分なスペースがないときにヒープを増やすことはそれほど難しくありません。ヒープを処理するライブラリ呼び出しに実装できます。ただし、スタックオーバーフローは手遅れの場合にのみ検出されるため、スタックを増やすことはしばしば不可能です。実行スレッドをシャットダウンすることが唯一の実行可能なオプションです。
|
次のC#コードでは
public void Method1()
{{
int i = 4;
int y = 2;
class1 cls1 = new class1();
}
メモリの管理方法は次のとおりです
関数の呼び出しがスタックにある間だけ持続する必要があるローカル変数。ヒープは、その存続期間が前もって実際にはわからない変数に使用されますが、それらはしばらく続くと予想されます。ほとんどの言語では、変数をスタックに格納する場合、コンパイル時に変数の大きさを知ることが重要です。
オブジェクト(更新するとサイズが異なります)は、作成時にオブジェクトがどのくらいの期間続くかわからないため、ヒープに移動します。多くの言語では、ヒープはガベージコレクションされて、参照がなくなったオブジェクト(cls1オブジェクトなど)を検索します。
Javaでは、ほとんどのオブジェクトが直接ヒープに入ります。 C / C ++のような言語では、ポインターを処理していないときに、構造体とクラスがスタックに残ることがよくあります。
詳細については、こちらをご覧ください。
スタックとヒープのメモリ割り当ての違い«timmurphy.org
そしてここ:
スタックとヒープにオブジェクトを作成する
この記事は上の図のソースです:6つの重要な.NET概念:スタック、ヒープ、値型、参照型、ボックス化、およびボックス化解除-CodeProject
ただし、いくつかの不正確さが含まれている可能性があることに注意してください。
|
スタック
関数を呼び出すと、その関数への引数とその他のオーバーヘッドがスタックに置かれます。いくつかの情報(帰りにどこに行くかなど)もそこに保存されます。
関数内で変数を宣言すると、その変数もスタックに割り当てられます。
スタックの割り当て解除は、常に割り当てとは逆の順序で割り当て解除するため、非常に簡単です。関数に入るとスタックのものが追加され、関数を終了すると対応するデータが削除されます。これは、他の多くの関数を呼び出す(または再帰的なソリューションを作成する)多くの関数を呼び出さない限り、スタックの小さな領域内にとどまる傾向があることを意味します。
ヒープ
ヒープは、作成したデータをその場で配置する場所の総称です。プログラムが作成する宇宙船の数がわからない場合は、新しい(またはmallocまたは同等の)演算子を使用して各宇宙船を作成する可能性があります。この割り当てはしばらく続くため、作成した順序とは異なる順序で解放される可能性があります。
したがって、ヒープははるかに複雑になります。これは、未使用のメモリ領域がチャンクとインターリーブされてしまうためです。メモリは断片化されます。必要なサイズの空きメモリを見つけるのは難しい問題です。これが、ヒープを回避する必要がある理由です(ただし、まだ頻繁に使用されています)。
実装
スタックとヒープの両方の実装は、通常、ランタイム/ OSに依存します。多くの場合、パフォーマンスが重要なゲームやその他のアプリケーションは、ヒープからメモリの大部分を取得し、メモリをOSに依存しないように内部でディッシュする、独自のメモリソリューションを作成します。
これは、メモリ使用量が標準とはかなり異なる場合にのみ実用的です。つまり、ある巨大な操作でレベルをロードし、別の巨大な操作で全体をチャックできるゲームの場合です。
メモリ内の物理的な場所
これは、仮想メモリと呼ばれるテクノロジによって、物理データが別の場所(ハードディスク上でも!)にある特定のアドレスにアクセスできるとプログラムに思わせるため、思ったほど関連性が低くなります。スタック用に取得するアドレスは、呼び出しツリーが深くなるにつれて昇順です。ヒープのアドレスは予測不可能であり(つまり、実装固有)、率直に言って重要ではありません。
|
明確にするために、この回答には誤った情報が含まれています(トーマスはコメントの後に彼の回答を修正しました、クール:))。他の答えは、静的割り当ての意味を説明することを避けています。そこで、割り当ての3つの主要な形式と、それらが通常、ヒープ、スタック、およびデータセグメントにどのように関連するかを以下で説明します。また、人々が理解しやすいように、C / C ++とPythonの両方でいくつかの例を示します。
「静的」(別名静的に割り当てられた)変数は、スタックに割り当てられません。そうは思わないでください。多くの人は、「静的」が「スタック」によく似ているという理由だけでそうします。それらは実際にはスタックにもヒープにも存在しません。これらは、データセグメントと呼ばれるものの一部です。
ただし、一般的には、「スタック」と「ヒープ」よりも「スコープ」と「ライフタイム」を考慮する方が適切です。
スコープとは、コードのどの部分が変数にアクセスできるかを指します。一般に、ローカルスコープ(現在の関数でのみアクセス可能)とグローバルスコープ(どこからでもアクセス可能)を考えますが、スコープははるかに複雑になる可能性があります。
ライフタイムとは、プログラムの実行中に変数が割り当ておよび割り当て解除される時期を指します。通常、静的割り当て(変数プログラムの全期間を通じて持続するため、複数の関数呼び出しにわたって同じ情報を格納するのに役立ちます)と自動割り当て(変数は関数への1回の呼び出し中にのみ持続するため、関数であり、完了したら破棄できます)と動的割り当て(静的または自動のようなコンパイル時間ではなく、実行時に期間が定義される変数)。
ほとんどのコンパイラとインタプリタは、スタックやヒープなどの使用に関して同様にこの動作を実装しますが、動作が正しい限り、コンパイラは必要に応じてこれらの規則に違反することがあります。たとえば、最適化により、ほとんどのローカル変数がスタックに存在する場合でも、ローカル変数はレジスタにのみ存在するか、完全に削除される可能性があります。いくつかのコメントで指摘されているように、スタックやヒープを使用せず、代わりに他のいくつかのストレージメカニズムを使用するコンパイラを自由に実装できます(スタックとヒープはこれに最適であるため、ほとんど実行されません)。
これらすべてを説明するために、いくつかの簡単な注釈付きCコードを提供します。学ぶための最良の方法は、デバッガーの下でプログラムを実行し、動作を監視することです。 Pythonを読みたい場合は、答えの最後までスキップしてください:)
//プログラム/ DLLが最初にロードされたときにデータセグメントに静的に割り当てられます
//プログラム/ DLLの終了時に割り当てが解除されます
//スコープ-コード内のどこからでもアクセスできます
int someGlobalVariable;
//プログラムが最初にロードされたときに、データセグメントに静的に割り当てられます
//プログラム/ DLLの終了時に割り当てが解除されます
//スコープ-この特定のコードファイルのどこからでもアクセスできます
static int someStaticVariable;
// MyFunctionが呼び出されるたびに、「someArgument」がスタックに割り当てられます
// MyFunctionが戻ると、「someArgument」の割り当てが解除されます
//スコープ-MyFunction()内でのみアクセスできます
void MyFunction(int someArgument){
//プログラムが最初にロードされたときに、データセグメントに静的に割り当てられます
//プログラム/ DLLの終了時に割り当てが解除されます
//スコープ-MyFunction()内でのみアクセスできます
static int someLocalStaticVariable;
// MyFunctionが呼び出されるたびにスタックに割り当てられます
// MyFunctionが戻ったときに割り当てが解除されました
//スコープ-MyFunction()内でのみアクセスできます
int someLocalVariable;
// MyFunctionが呼び出されるたびに*ポインタ*がスタックに割り当てられます
// MyFunctionが戻ると、このポインタの割り当てが解除されます
//スコープ-ポインタはMyFunction()内でのみアクセスできます
int * someDynamicVariable;
//この行により、整数用のスペースがヒープに割り当てられます
//この行が実行されたとき。これは最初ではないことに注意してください
//自動変数のように、MyFunction()の呼び出し
//スコープ-MyFunction()内のコードのみがこのスペースにアクセスできます
// *この特定の変数を介して*。
//ただし、アドレスを別の場所に渡すと、そのコード
//アクセスすることもできます
someDynamicVariable = new int;
//この行は、ヒープ内の整数のスペースの割り当てを解除します。
//書き込まなかった場合、メモリが「リーク」します。
//スタックとヒープの根本的な違いに注意してください
//ヒープを管理する必要があります。スタックは私たちのために管理されています。
someDynamicVariableを削除します。
//その他の場合、このヒープスペースの割り当てを解除する代わりに、
//後で使用するために、アドレスをより永続的な場所に保存する場合があります。
//一部の言語では、割り当て解除も処理されます...しかし
//常に何らかのメカニズムによって実行時に処理する必要があります。
//関数が戻ると、someArgument、someLocalVariable
//およびポインタsomeDynamicVariableの割り当てが解除されます。
// someDynamicVariableが指すスペースはすでに存在していました
//戻る前に割り当てを解除します。
戻る;
}
// someGlobalVariable、someStaticVariable、および
// someLocalStaticVariableは引き続き存在し、存在しません
//プログラムが終了するまで割り当てを解除します。
ライフタイムとスコープを区別することが重要である理由の特に痛烈な例は、変数がローカルスコープを持つことができるが静的ライフタイムを持つことができることです-たとえば、上記のコードサンプルの「someLocalStaticVariable」。このような変数は、私たちの一般的ではあるが非公式な命名習慣を非常に混乱させる可能性があります。たとえば、「ローカル」と言うときは通常「ローカルスコープの自動的に割り当てられた変数」を意味し、グローバルと言うときは通常「グローバルスコープの静的に割り当てられた変数」を意味します。残念ながら、「ファイルスコープの静的に割り当てられた変数」のようなものになると、多くの人がただ「えっ???」と言います。
C / C ++での構文の選択のいくつかは、この問題を悪化させます。たとえば、多くの人々は、以下に示す構文のために、グローバル変数は「静的」ではないと考えています。
int var1; //グローバルスコープと静的割り当てがあります
static int var2; //ファイルスコープと静的割り当てがあります
int main(){return 0;}
上記の宣言にキーワード「static」を入れると、var2がグローバルスコープを持つことができなくなることに注意してください。それにもかかわらず、グローバルvar1には静的割り当てがあります。これではありません直感的!このため、スコープを説明するときに「静的」という言葉を使用せず、代わりに「ファイル」や「ファイル制限」スコープなどを使用するようにしています。ただし、多くの人は「静的」または「静的スコープ」というフレーズを使用して、1つのコードファイルからのみアクセスできる変数を説明します。ライフタイムのコンテキストでは、「静的」とは常に、変数がプログラムの開始時に割り当てられ、プログラムの終了時に割り当て解除されることを意味します。
一部の人々は、これらの概念をC / C ++固有のものと考えています。ではない。たとえば、以下のPythonサンプルは、3つのタイプの割り当てすべてを示しています(ここでは説明しませんが、インタープリター言語では微妙な違いがいくつかあります)。
日時からインポート日時
クラス動物:
_FavoriteFood = '未定義' #_FavoriteFoodは静的に割り当てられます
def PetAnimal(self):
curTime = datetime.time(datetime.now())#curTimeは自動的に割り当てられます
print( "愛撫してくれてありがとう。でも" + str(curTime)+ "、食べさせてくれ。私の好きな食べ物は" + self._FavoriteFood)
クラスCat(Animal):
_FavoriteFood = 'tuna'#オーバーライドするため、Catクラスには、Animalとは異なり、静的に割り当てられた独自の_FavoriteFood変数があります。
クラス犬(動物):
_FavoriteFood = 'steak'#同様に、Dogクラスは独自の静的変数を取得します。注意すべき重要な点-この1つの静的変数は、Dogのすべてのインスタンス間で共有されるため、動的ではありません。
if __name__ == "__main __":
ひげ= Cat()#動的に割り当てられる
fido = Dog()#動的に割り当てられます
rinTinTin = Dog()#動的に割り当てられます
ひげ.PetAnimal()
fido.PetAnimal()
rinTinTin.PetAnimal()
Dog._FavoriteFood = 'milkbones'
ひげ.PetAnimal()
fido.PetAnimal()
rinTinTin.PetAnimal()
#出力は次のとおりです。
#私をかわいがってくれてありがとう。しかし、それは13:05:02.255000です、あなたは私を養うべきです。私の好きな食べ物はマグロです
#私をかわいがってくれてありがとう。しかし、それは13:05:02.255000です、あなたは私を養うべきです。私の好きな食べ物はステーキです
#私をかわいがってくれてありがとう。しかし、それは13:05:02.255000です、あなたは私を養うべきです。私の好きな食べ物はステーキです
#私をかわいがってくれてありがとう。しかし、それは13:05:02.255000です、あなたは私を養うべきです。私の好きな食べ物はマグロです
#私をかわいがってくれてありがとう。しかし、それは13:05:02.255000です、あなたは私を養うべきです。私の好きな食べ物はミルクボーンです
#私をかわいがってくれてありがとう。しかし、それは13:05:02.256000です、あなたは私を養うべきです。私の好きな食べ物はミルクボーンです
|
他の人は幅広いストロークにかなりよく答えているので、いくつか詳細を紹介します。
スタックとヒープは単一である必要はありません。複数のスタックがある一般的な状況は、プロセスに複数のスレッドがある場合です。この場合、各スレッドには独自のスタックがあります。複数のヒープを持つこともできます。たとえば、一部のDLL構成では、異なるヒープから異なるDLLが割り当てられる可能性があるため、異なるライブラリによって割り当てられたメモリを解放することは一般的にお勧めできません。
Cでは、ヒープに割り当てるallocとは対照的に、スタックに割り当てるallocaを使用することで、可変長割り当ての利点を得ることができます。このメモリはreturnステートメントに耐えられませんが、スクラッチバッファには役立ちます。
あまり使用しないWindowsで巨大な一時バッファを作成するのは無料ではありません。これは、コンパイラがスタックが存在することを確認するために関数が入力されるたびに呼び出されるスタックプローブループを生成するためです(Windowsはスタックの最後にある単一のガードページを使用して、スタックを拡張する必要があるときを検出するためです。スタックの最後から1ページ以上離れたメモリにアクセスすると、クラッシュします)。例:
void myfunction()
{{
char big [10000000];
// 99%の時間の最初の1Kにのみ使用することを行います。
}
|
他の人があなたの質問に直接答えましたが、スタックとヒープを理解しようとするときは、従来のUNIXプロセス(スレッドとmmap()ベースのアロケーターなし)のメモリレイアウトを検討することが役立つと思います。メモリ管理用語集のWebページには、このメモリレイアウトの図があります。
スタックとヒープは、従来、プロセスの仮想アドレス空間の両端に配置されていました。スタックは、アクセス時にカーネルによって設定されたサイズまで自動的に大きくなります(setrlimit(RLIMIT_STACK、...)で調整できます)。メモリアロケータがbrk()またはsbrk()システムコールを呼び出し、物理メモリのより多くのページをプロセスの仮想アドレス空間にマッピングすると、ヒープが大きくなります。
一部の組み込みシステムなど、仮想メモリのないシステムでは、スタックとヒープのサイズが固定されていることを除いて、同じ基本レイアウトが適用されることがよくあります。ただし、他の組み込みシステム(Microchip PICマイクロコントローラーに基づくシステムなど)では、プログラムスタックは、データ移動命令ではアドレス指定できない別個のメモリブロックであり、プログラムフロー命令(呼び出し、返品など)。 Intel Itaniumプロセッサなどの他のアーキテクチャには、複数のスタックがあります。この意味で、スタックはCPUアーキテクチャの要素です。
|
スタックは一部です'pop'(スタックから値を削除して返す)や 'push'(スタックに値をプッシュする)などのいくつかの主要なアセンブリ言語命令を介して操作できるメモリの量だけでなく、呼び出し(サブルーチンを呼び出す-これアドレスをプッシュしてスタックに戻します)そして戻ります(サブルーチンから戻ります-これはスタックからアドレスをポップしてそれにジャンプします)。これは、スタックポインタレジスタの下のメモリ領域であり、必要に応じて設定できます。スタックは、サブルーチンに引数を渡すため、およびサブルーチンを呼び出す前にレジスタの値を保持するためにも使用されます。
ヒープは、オペレーティングシステムによって、通常はmallocのようなシステムコールを介してアプリケーションに提供されるメモリの一部です。最新のOSでは、このメモリは呼び出しプロセスのみがアクセスできるページのセットです。
スタックのサイズは実行時に決定され、通常、プログラムの起動後に大きくなることはありません。 Cプログラムでは、スタックは、各関数内で宣言されたすべての変数を保持するのに十分な大きさである必要があります。ヒープは必要に応じて動的に拡張されますが、OSは最終的に呼び出しを行います(多くの場合、ヒープはmallocが要求する値よりも大きくなるため、少なくとも一部の将来のmallocはカーネルに戻ってより多くのメモリを取得します。この動作は多くの場合カスタマイズ可能です)
プログラムを起動する前にスタックを割り当てているので、スタックを使用する前にmallocを実行する必要はありません。これは、わずかな利点です。実際には、仮想メモリサブシステムを備えた最新のオペレーティングシステムでは、ページの実装方法と保存場所が実装の詳細であるため、何が高速で何が低速になるかを予測することは非常に困難です。
|
スタックとは何ですか?
スタックはオブジェクトの山であり、通常はきちんと配置されています。
コンピューティングアーキテクチャのスタックは、データが後入れ先出し方式で追加または削除されるメモリ領域です。
マルチスレッドアプリケーションでは、各スレッドに独自のスタックがあります。
ヒープとは何ですか?
ヒープは、無計画に積み上げられたものの乱雑なコレクションです。
コンピューティングアーキテクチャでは、ヒープは動的に割り当てられたメモリの領域であり、オペレーティングシステムまたはメモリマネージャライブラリによって自動的に管理されます。
ヒープ上のメモリは、プログラムの実行中に定期的に割り当て、割り当て解除、およびサイズ変更されます。これにより、フラグメンテーションと呼ばれる問題が発生する可能性があります。
断片化は、メモリオブジェクトが、追加のメモリオブジェクトを保持するには小さすぎる小さなスペースを間に置いて割り当てられた場合に発生します。
最終的な結果は、それ以上のメモリ割り当てに使用できないヒープスペースのパーセンテージです。
両方一緒に
マルチスレッドアプリケーションでは、各スレッドに独自のスタックがあります。ただし、すべての異なるスレッドがヒープを共有します。
異なるスレッドはマルチスレッドアプリケーションでヒープを共有するため、これは、スレッドがヒープ内の同じメモリにアクセスして操作しようとしないように、スレッド間に何らかの調整が必要であることも意味します。同時に。
スタックとヒープのどちらが速いですか?なぜ?
スタックはヒープよりもはるかに高速です。
これは、メモリがスタックに割り当てられる方法によるものです。
スタックへのメモリの割り当ては、スタックポインタを上に移動するのと同じくらい簡単です。
プログラミングに不慣れな人にとっては、スタックの方が簡単なので、おそらくスタックを使用することをお勧めします。
スタックは小さいため、データに必要なメモリの量が正確にわかっている場合、またはデータのサイズが非常に小さいことがわかっている場合に使用することをお勧めします。
データに大量のメモリが必要であることがわかっている場合、または必要なメモリ量がわからない場合(動的配列など)は、ヒープを使用することをお勧めします。
Javaメモリモデル
スタックは、ローカル変数(メソッドパラメーターを含む)が格納されるメモリの領域です。オブジェクト変数に関して言えば、これらはヒープ上の実際のオブジェクトへの単なる参照(ポインター)です。
オブジェクトがインスタンス化されるたびに、ヒープメモリのチャンクがそのオブジェクトのデータ(状態)を保持するために確保されます。オブジェクトには他のオブジェクトを含めることができるため、このデータの一部は実際にはそれらのネストされたオブジェクトへの参照を保持できます。
|
他の多くの人がこの問題についてあなたにほとんど正しい答えを与えたと思います。
ただし、見逃されている詳細の1つは、「ヒープ」は実際にはおそらく「フリーストア」と呼ばれるべきであるということです。この区別の理由は、元のフリーストアが「二項ヒープ」と呼ばれるデータ構造で実装されていたためです。そのため、malloc()/ free()の初期の実装からの割り当ては、ヒープからの割り当てでした。ただし、この現代では、ほとんどの無料ストアは、二項ヒープではない非常に複雑なデータ構造で実装されています。
|
あなたはスタックでいくつかの面白いことをすることができます。たとえば、allocaのような機能があります(その使用に関する大量の警告を乗り越えることができると仮定します)。これはmallocの形式です。具体的には、メモリにヒープではなくスタックを使用します。
とはいえ、スタックベースのメモリエラーは私が経験した中で最悪のもののいくつかです。ヒープメモリを使用していて、割り当てられたブロックの境界を超えた場合、セグメントフォールトをトリガーする可能性が十分にあります。 (100%ではありません:ブロックが以前に割り当てた別のブロックと偶然に隣接している可能性があります。)ただし、スタックに作成された変数は常に互いに隣接しているため、範囲外に書き込むと別の変数の値が変わる可能性があります。私のプログラムが論理の法則に従わなくなったと感じるときはいつでも、それはおそらくバッファオーバーフローであることを学びました。
|
簡単に言うと、スタックはローカル変数が作成される場所です。また、サブルーチンを呼び出すたびに、プログラムカウンタ(次のマシン命令へのポインタ)と重要なレジスタがあり、パラメータがスタックにプッシュされることもあります。次に、サブルーチン内のローカル変数がスタックにプッシュされます(そしてそこから使用されます)。サブルーチンが終了すると、そのすべてがスタックからポップバックされます。 PCとレジスタのデータは、ポップされたときの状態に戻って取得されるため、プログラムは順調に進むことができます。
ヒープは、動的メモリ割り当てが行われるメモリの領域です(明示的な「新規」または「割り当て」呼び出し)。これは、さまざまなサイズのメモリブロックとその割り当てステータスを追跡できる特別なデータ構造です。
「クラシック」システムでは、RAMは、スタックポインタがメモリの下部から始まり、ヒープポインタが上部から始まり、互いに向かって大きくなるように配置されていました。それらが重複している場合は、RAMが不足しています。ただし、これは最新のマルチスレッドOSでは機能しません。すべてのスレッドには独自のスタックが必要であり、それらは動的に作成できます。
|
WikiAnwserから。
スタック
関数またはメソッドが別の関数を呼び出し、次に別の関数を呼び出すなどの場合、最後の関数がその値を返すまで、それらすべての関数の実行は中断されたままになります。
スタック内の要素(関数呼び出し)は相互に依存しているため、この中断された関数呼び出しのチェーンはスタックです。
スタックは、例外処理とスレッド実行で考慮することが重要です。
ヒープ
ヒープは、プログラムが変数を格納するために使用するメモリです。
ヒープの要素(変数)は相互に依存関係がなく、いつでもランダムにアクセスできます。
|
スタック
非常に高速なアクセス
変数の割り当てを明示的に解除する必要はありません
スペースはCPUによって効率的に管理され、メモリは断片化されません
ローカル変数のみ
スタックサイズの制限(OSに依存)
変数のサイズを変更することはできません
ヒープ
変数にはグローバルにアクセスできます
メモリサイズに制限はありません
(比較的)遅いアクセス
スペースの効率的な使用は保証されていません。メモリのブロックが割り当てられてから解放されると、メモリは時間の経過とともに断片化する可能性があります
メモリを管理する必要があります(変数の割り当てと解放を担当します)
realloc()を使用して変数のサイズを変更できます
|
要するに
スタックは静的メモリ割り当てに使用され、ヒープは動的メモリ割り当てに使用され、どちらもコンピュータのRAMに格納されます。
詳細に
スタック
スタックは「LIFO」(後入れ先出し)データ構造であり、CPUによって非常に厳密に管理および最適化されます。関数が新しい変数を宣言するたびに、その変数はスタックに「プッシュ」されます。その後、関数が終了するたびに、その関数によってスタックにプッシュされたすべての変数が解放されます(つまり、それらは削除されます)。スタック変数が解放されると、そのメモリ領域は他のスタック変数で使用できるようになります。
スタックを使用して変数を格納する利点は、メモリが管理されることです。手動でメモリを割り当てたり、不要になったメモリを解放したりする必要はありません。さらに、CPUはスタックメモリを非常に効率的に編成するため、スタック変数の読み取りと書き込みは非常に高速です。
詳細はこちらをご覧ください。
ヒープ
ヒープは、コンピュータのメモリの領域であり、自動的に管理されることはなく、CPUによって厳密に管理されることもありません。それはメモリのより自由に動く領域です(そしてより大きくなります)。ヒープにメモリを割り当てるには、組み込みのC関数であるmalloc()またはcalloc()を使用する必要があります。ヒープにメモリを割り当てたら、free()を使用して、メモリが不要になったらそのメモリの割り当てを解除する必要があります。
これを怠ると、プログラムにメモリリークと呼ばれるものが発生します。つまり、ヒープ上のメモリは引き続き確保されます(そして、他のプロセスで使用できなくなります)。デバッグのセクションで説明するように、メモリリークの検出に役立つValgrindというツールがあります。
スタックとは異なり、ヒープには可変サイズのサイズ制限がありません(コンピューターの明らかな物理的制限は別として)。ヒープメモリは、ヒープ上のメモリにアクセスするためにポインタを使用する必要があるため、読み取りと書き込みが少し遅くなります。ポインタについては後ほど説明します。
スタックとは異なり、ヒープ上に作成された変数には、プログラム内の任意の関数からアクセスできます。ヒープ変数は基本的にグローバルなスコープです。
詳細はこちらをご覧ください。
スタックに割り当てられた変数はメモリに直接格納され、このメモリへのアクセスは非常に高速であり、その割り当てはプログラムのコンパイル時に処理されます。関数またはメソッドが別の関数を呼び出し、次に別の関数を呼び出すなどの場合、最後の関数がその値を返すまで、それらすべての関数の実行は中断されたままになります。スタックは常にLIFOの順序で予約され、最後に予約されたブロックは常に次に解放されるブロックです。これにより、スタックの追跡が非常に簡単になります。スタックからブロックを解放することは、1つのポインターを調整するだけです。
ヒープに割り当てられた変数のメモリは実行時に割り当てられ、このメモリへのアクセスは少し遅くなりますが、ヒープサイズは仮想メモリのサイズによってのみ制限されます。ヒープの要素は相互に依存関係がなく、いつでもランダムにアクセスできます。ブロックはいつでも割り当てて、いつでも解放できます。これにより、ヒープのどの部分がいつでも割り当てられているか、解放されているかを追跡することがはるかに複雑になります。
コンパイル時までに割り当てる必要のあるデータの量が正確にわかっていて、大きすぎない場合は、スタックを使用できます。実行時に必要なデータの量が正確にわからない場合、または大量のデータを割り当てる必要がある場合は、ヒープを使用できます。
マルチスレッドの状況では、各スレッドは独自の完全に独立したスタックを持ちますが、ヒープを共有します。スタックはスレッド固有であり、ヒープはアプリケーション固有です。スタックは、例外処理とスレッド実行で考慮することが重要です。
各スレッドはスタックを取得しますが、通常、アプリケーションのヒープは1つだけです(ただし、さまざまなタイプの割り当てに対して複数のヒープがあることは珍しくありません)。
実行時に、アプリケーションがより多くのヒープを必要とする場合、空きメモリからメモリを割り当てることができ、スタックがメモリを必要とする場合、アプリケーションに割り当てられた空きメモリからメモリを割り当てることができます。
さらに、こことここに詳細が示されています。
今、あなたの質問の答えに来てください。
それらはOSまたは言語ランタイムによってどの程度制御されていますか?
OSは、スレッドの作成時に、システムレベルのスレッドごとにスタックを割り当てます。通常、OSは言語ランタイムによって呼び出され、アプリケーションにヒープを割り当てます。
詳細はこちらをご覧ください。
それらの範囲は何ですか?
すでに上に与えられています。
「コンパイル時に割り当てる必要のあるデータの量が正確にわかっていて、大きすぎない場合は、スタックを使用できます。実行時に必要なデータの量が正確にわからない場合、または次の場合は、ヒープを使用できます。大量のデータを割り当てる必要があります。」
詳細はこちらをご覧ください。
それらのそれぞれのサイズを決定するものは何ですか?
スタックのサイズは、スレッドの作成時にOSによって設定されます。ヒープのサイズはアプリケーションの起動時に設定されますが、スペースが必要になると大きくなる可能性があります(アロケーターはオペレーティングシステムからより多くのメモリを要求します)。
何が速くなりますか?
実際に行うのはスタックポインタを移動することだけなので、スタック割り当てははるかに高速です。メモリプールを使用すると、ヒープ割り当てから同等のパフォーマンスを得ることができますが、それには少し複雑さが増し、それ自体が頭痛の種になります。
また、スタックとヒープはパフォーマンスの考慮事項だけではありません。また、オブジェクトの予想寿命について多くのことを教えてくれます。
詳細はこちらから。
|
OK、簡単に言えば、それらは注文されたものと注文されていないものを意味します...!
スタック:スタックアイテムでは、物事が互いに重なり合うため、処理がより速く、より効率的になります!...
したがって、特定のアイテムを指すインデックスが常にあり、処理も高速になります。アイテム間の関係もあります!...
ヒープ:順序がなく、処理が遅くなり、特定の順序やインデックスがないために値が混乱します...ランダムであり、それらの間に関係はありません...したがって、実行時間と使用時間は異なる可能性があります...
また、以下の画像を作成して、どのように見えるかを示します。
|
仮想メモリ内の各プロセスのスタック、ヒープ、およびデータ:
|
1980年代、UNIXは大企業が独自に転がり、うさぎのように広まりました。
エクソンには、歴史に失われた何十ものブランド名と同じように1つありました。
メモリがどのように配置されたかは、多くの実装者の裁量でした。
典型的なCプログラムは、メモリ内にフラットに配置されました。
brk()値を変更することで増加する機会。
通常、HEAPはこのbrk値をわずかに下回りました
brkを増やすと、使用可能なヒープの量が増えました。
単一のスタックは通常、メモリの領域であるHEAPの下の領域でした。
次の固定メモリブロックの先頭まで、値は何も含まれていません。
この次のブロックは、スタックデータで上書きされる可能性のあるCODEであることがよくありました
その時代の有名なハッキングの1つで。
1つの典型的なメモリブロックはBSS(ゼロのブロック)でした値)
これは、あるメーカーの製品で誤ってゼロにされていませんでした。
もう1つは、文字列や数値などの初期化された値を含むDATAでした。
3つ目は、CRT(Cランタイム)、メイン、関数、およびライブラリを含むCODEでした。
UNIXでの仮想メモリの出現により、多くの制約が変わりました。
これらのブロックが連続している必要があるという客観的な理由はありません。
またはサイズが固定されているか、特定の方法で注文しました。
もちろん、UNIXが登場する前は、これらの制約に悩まされていなかったMulticsでした。
これは、その時代のメモリレイアウトの1つを示す回路図です。
|
数セント:メモリをグラフィカルでより単純に描くのが良いと思います:
矢印-成長スタックとヒープ、プロセススタックサイズに制限があり、OSで定義されている場所、通常はスレッド作成APIのパラメーターによるスレッドスタックサイズの制限を示します。ヒープは通常、プロセスの最大仮想メモリサイズによって制限されます。たとえば、32ビット2〜4GBです。
とても簡単な方法:プロセスヒープはプロセスとその中のすべてのスレッドに一般的であり、malloc()のようなもので一般的なケースでメモリ割り当てに使用されます。
スタックは、一般的な場合に関数の戻りポインタと変数を格納するためのクイックメモリであり、関数呼び出し、ローカル関数変数のパラメータとして処理されます。
|
いくつかの答えがつまらなくなったので、私は私のダニに貢献するつもりです。
驚いたことに、複数の(つまり、実行中のOSレベルのスレッドの数に関係のない)コールスタックが、エキゾチックな言語(PostScript)やプラットフォーム(Intel Itanium)だけでなく、ファイバーやグリーンスレッドにも見られるとは誰も言及していませんコルーチンのいくつかの実装。
繊維、グリーンスレッド、コルーチンは多くの点で類似しており、多くの混乱を招きます。ファイバーとグリーンスレッドの違いは、前者は協調マルチタスクを使用するのに対し、後者は協調型またはプリエンプティブ型(あるいはその両方)を備えている場合があることです。ファイバーとコルーチンの違いについては、こちらをご覧ください。
いずれにせよ、ファイバー、グリーンスレッド、コルーチンの両方の目的は、複数の関数を同時に実行することですが、単一のOSレベルのスレッド内で並行して実行することはできません(区別についてはこのSOの質問を参照)。組織化された方法で。
ファイバー、グリーンスレッド、またはコルーチンを使用する場合、通常、関数ごとに個別のスタックがあります。 (技術的には、スタックだけでなく、実行のコンテキスト全体が関数ごとにあります。最も重要なのは、CPUレジスタです。)スレッドごとに、同時に実行されている関数と同じ数のスタックがあり、スレッドは各関数の実行を切り替えます。プログラムのロジックに従って。関数が最後まで実行されると、そのスタックは破棄されます。したがって、スタックの数と存続期間は動的であり、OSレベルのスレッドの数によって決定されるわけではありません。
「通常、関数ごとに個別のスタックがある」と言ったことに注意してください。クールルーチンには、スタックフルとスタックレスの両方の実装があります。最も注目すべきスタックフルC ++実装は、Boost.CoroutineとMicrosoftPPLのasync / awaitです。 (ただし、C ++ 17に提案されたC ++の再開可能な関数(別名「asyncand await」)は、スタックレスコルーチンを使用する可能性があります。)
C ++標準ライブラリへのファイバーの提案が間もなく開始されます。また、いくつかのサードパーティライブラリがあります。グリーンスレッドは、PythonやRubyなどの言語で非常に人気があります。
|
主要なポイントはすでにカバーされていますが、私は共有することがあります。
スタック
非常に高速なアクセス。
RAMに保存されます。
関数呼び出しは、渡されたローカル変数と関数パラメーターとともにここにロードされます。
プログラムがスコープ外になると、スペースは自動的に解放されます。
シーケンシャルメモリに保存されます。
ヒープ
スタックに比べてアクセスが遅い。
RAMに保存されます。
動的に作成された変数はここに保存され、後で使用後に割り当てられたメモリを解放する必要があります。
メモリ割り当てが行われる場所に格納され、常にポインタによってアクセスされます。
興味深いメモ:
関数呼び出しがヒープに格納されていたとしたら、2つの厄介な点が発生していました。
スタックにシーケンシャルストレージがあるため、実行が高速になります。ヒープに格納すると、膨大な時間が消費されるため、プログラム全体の実行速度が低下します。
関数がヒープ(ポインターが指す乱雑なストレージ)に格納されている場合、呼び出し元のアドレスに戻る方法はありませんでした(メモリ内のシーケンシャルストレージのためにスタックが提供します)。
|
うわー!非常に多くの答えがあり、そのうちの1つが正しく理解できなかったと思います...
1)それらはどこに何がありますか(物理的には実際のコンピューターのメモリ内)?
スタックは、プログラムイメージに割り当てられた最大のメモリアドレスとして始まり、そこから値が減少するメモリです。これは、呼び出された関数パラメーターおよび関数で使用されるすべての一時変数用に予約されています。
パブリックとプライベートの2つのヒープがあります。
プライベートヒープは、プログラムのコードの最後のバイトの後の16バイト境界(64ビットプログラムの場合)または8バイト境界(32ビットプログラムの場合)で始まり、その後増加します。そこからの価値。デフォルトヒープとも呼ばれます。
プライベートヒープが大きくなりすぎると、スタック領域とオーバーラップします。スタックが大きくなりすぎると、スタックがオーバーラップします。スタックは高いアドレスから始まり、低いアドレスに向かって進むため、適切なハッキングを行うと、スタックを非常に大きくして、プライベートヒープ領域をオーバーランさせ、コード領域とオーバーラップさせることができます。その場合の秘訣は、コードにフックできる十分なコード領域をオーバーラップさせることです。実行するのは少しトリッキーで、プログラムがクラッシュするリスクがありますが、簡単で非常に効果的です。
パブリックヒープは、プログラムイメージスペースの外側にある独自のメモリスペースにあります。メモリリソースが不足した場合にハードディスクに吸い上げられるのはこのメモリです。
2)OSまたは言語ランタイムによってどの程度制御されていますか?
スタックはプログラマーによって制御され、プライベートヒープはOSによって管理され、パブリックヒープはOSサービスであるため、誰によっても制御されません。要求を行うと、要求が許可または拒否されます。
2b)その範囲は何ですか?
それらはすべてプログラムに対してグローバルですが、その内容はプライベート、パブリック、またはグローバルにすることができます。
2c)それぞれのサイズを決定するものは何ですか?
スタックとプライベートヒープのサイズは、コンパイラのランタイムオプションによって決まります。パブリックヒープは、サイズパラメータを使用して実行時に初期化されます。
2d)何が速くなるのですか?
それらは高速になるように設計されているのではなく、便利になるように設計されています。プログラマーがそれらをどのように利用するかによって、それらが「速い」か「遅い」かが決まります。
参照:
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
|
概念としては多くの答えが正しいですが、サブルーチン(アセンブリ言語のCALL ..)を呼び出すには、ハードウェア(つまりマイクロプロセッサ)にスタックが必要であることに注意する必要があります。 (OOPの人はそれをメソッドと呼びます)
スタックにリターンアドレスを保存し、call→push / ret→popはハードウェアで直接管理されます。
スタックを使用してパラメーターを渡すことができます。レジスターを使用するよりも遅い場合でも(マイクロプロセッサーの第一人者または1980年代の優れたBIOSブック...)
スタックがないと、マイクロプロセッサは機能しません。 (アセンブリ言語であっても、サブルーチン/関数のないプログラムを想像することはできません)
ヒープがなくても可能です。 (ヒープはOSの概念であり、mallocのように、OS / Lib呼び出しであるため、アセンブリ言語プログラムは機能しません。
スタックの使用は次のように高速です。
ハードウェアであり、プッシュ/ポップでさえ非常に効率的です。
mallocでは、カーネルモードに入り、ロック/セマフォ(または他の同期プリミティブ)を使用してコードを実行し、割り当てを追跡するために必要な構造を管理する必要があります。
|
ヒープは、オペレーティングシステムまたはメモリマネージャライブラリによって自動的に管理される、動的に割り当てられたメモリの領域です。ブロックはいつでも割り当てて、いつでも解放できます。ヒープの割り当てには、割り当てられているメモリと割り当てられていないメモリの完全な記録を維持する必要があります。また、断片化を減らし、要求されたサイズに合うのに十分な大きさの連続したメモリセグメントを見つけるなどのオーバーヘッドの維持も必要です。空き領域を残して、いつでもメモリの割り当てを解除できます。ヒープが大きくなると、新しいブロックが低いアドレスから高いアドレスに割り当てられることがよくあります。したがって、ヒープは、メモリが割り当てられるにつれてサイズが大きくなるメモリブロックのヒープと考えることができます。ヒープが割り当てに対して小さすぎる場合は、基盤となるオペレーティングシステムからより多くのメモリを取得することで、サイズを大きくすることができます。ヒープから割り当てられたメモリは、次のいずれかが発生するまで割り当てられたままになります。
メモリが解放されます
プログラムは終了します
スタック:
ヒープと同じようにコンピュータのRAMに保存されます。
スタック上に作成された変数はスコープ外になり、自動的に割り当てが解除されます。
ヒープ上の変数と比較して、割り当てるのがはるかに高速です。
パラメータの受け渡しに使用されるローカルデータ、リターンアドレスを格納します。
スタックの使用量が多すぎると、スタックオーバーフローが発生する可能性があります(ほとんどの場合
無限または深すぎる再帰、非常に大きな割り当てから)。
必要なデータ量が正確にわかっている場合は、スタックを使用します
コンパイル時の前に割り当ててください。大きすぎません。
通常、プログラムの最大サイズはすでに決定されています
が始まります。
ヒープ:
スタックと同じようにコンピュータのRAMに保存されます。
C ++では、ヒープ上の変数は手動で破棄する必要があり、決して破棄しないでください
範囲外になります。
データは、delete、delete []、またはfreeで解放されます。
スタック上の変数と比較して、割り当てが遅くなります。
プログラムで使用するデータのブロックを割り当てるためにオンデマンドで使用されます。
割り当てが多く、
割り当て解除。
C ++またはCでは、ヒープ上に作成されたデータはポインターによってポイントされます
それぞれnewまたはmallocで割り当てられます。
大きすぎるバッファが要求された場合、割り当てが失敗する可能性があります
割り当てられます。
君はデータの量が正確にわからない場合は、ヒープを使用します
実行時、または大量のデータを割り当てる必要がある場合に必要になります。
メモリリークの原因です。
|
スタックは基本的に、アイテムを管理するだけのアクセスしやすいメモリです。
-よく-スタックとして。サイズが事前にわかっているアイテムのみがスタックに入ることができます。これは、数値、文字列、ブール値の場合です。
ヒープは、事前に決定できないアイテムのメモリです。
正確なサイズと構造。オブジェクトと配列は変更できるため、
実行時に変更すると、ヒープに入る必要があります。
出典:Academind
|
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アーキテクチャーソフトウェア開発者マニュアル
|
本当に良い議論をありがとう、しかし本当の初心者として、私は指示がどこに保管されているのだろうか?初めに、科学者は2つのアーキテクチャ(すべてがデータと見なされるフォンノイマンと、メモリの領域が命令用に予約されたハーバードとデータ用に予約されたハーバード)のどちらかを決定していました。最終的には、フォンノイマン型の設計を採用しましたが、今ではすべてが「同じ」と見なされています。これは私がアセンブリを学んでいたときに私にとって困難でした
https://www.cs.virginia.edu/~evans/cs216/guides/x86.html
彼らはレジスタとスタックポインタについて話しているからです。
上記のすべてがデータについて語っています。私の推測では、命令は特定のメモリフットプリントを持つ定義済みのものであるため、スタックに配置され、アセンブリで説明されているすべての「これらの」レジスタはスタックに配置されます。もちろん、その後、動的な構造に命令とデータが混在するオブジェクト指向プログラミングが登場したので、命令もヒープに保持されるようになりましたか?
|
非常に活発な質問。この質問に答えるために10の評判を獲得してください。レピュテーション要件は、この質問をスパムや無回答のアクティビティから保護するのに役立ちます。
あなたが探している答えではありませんか?メモリ管理スタック言語に依存しないヒープ動的メモリ割り当てのタグが付けられた他の質問を参照するか、独自の質問をしてください。