プログラミング言語の本では、これらの2つが何であるかを説明することなく、値型はスタック上に作成され、参照型はヒープ上に作成されると説明されています。私はこれの明確な説明を読んでいません。私は何を理解していますスタックとはですか。だが、
- それらはどこにありますか(物理的には実際のコンピュータのメモリ内)?
- それらは、OSまたは言語ランタイムによってどの程度制御されていますか?
- それらの範囲は何ですか?
- それぞれのサイズを決定するものは何ですか?
- 何が速くなるのですか?
プログラミング言語の本では、これらの2つが何であるかを説明することなく、値型はスタック上に作成され、参照型はヒープ上に作成されると説明されています。私はこれの明確な説明を読んでいません。私は何を理解していますスタックとはですか。だが、
回答:
スタックは、実行スレッドのスクラッチスペースとして確保されるメモリです。関数が呼び出されると、ブロックはスタックの最上部にローカル変数と一部の簿記データ用に予約されます。その関数が戻ると、ブロックは未使用になり、次に関数が呼び出されたときに使用できます。スタックは常にLIFO(後入れ先出し)の順序で予約されています。最後に予約されたブロックは、常に次に解放されるブロックです。これにより、スタックの追跡が非常に簡単になります。スタックからブロックを解放することは、1つのポインタを調整することに他なりません。
ヒープは、動的割り当て用に確保されるメモリです。スタックとは異なり、ヒープからのブロックの割り当てと割り当て解除には強制パターンはありません。ブロックはいつでも割り当てて、いつでも解放できます。これにより、ヒープのどの部分がいつでも割り当てられたり解放されたりするかを追跡するのがはるかに複雑になります。さまざまな使用パターンに合わせてヒープパフォーマンスを調整するために使用できるカスタムヒープアロケーターは多数あります。
各スレッドはスタックを取得しますが、アプリケーションには通常1つのヒープしかありません(ただし、割り当ての種類ごとに複数のヒープがあることは珍しくありません)。
質問に直接回答するには:
OSまたは言語ランタイムによってどの程度制御されていますか?
OSは、スレッドが作成されるときに、システムレベルのスレッドごとにスタックを割り当てます。通常、OSは、アプリケーションにヒープを割り当てるために言語ランタイムによって呼び出されます。
それらの範囲は何ですか?
スタックはスレッドにアタッチされているため、スレッドが終了すると、スタックは再利用されます。ヒープは通常、アプリケーションの起動時にランタイムによって割り当てられ、アプリケーション(技術的にプロセス)が終了すると再利用されます。
それぞれのサイズを決定するものは何ですか?
スタックのサイズは、スレッドの作成時に設定されます。ヒープのサイズはアプリケーションの起動時に設定されますが、スペースが必要になると大きくなる可能性があります(アロケーターはオペレーティングシステムからより多くのメモリを要求します)。
何が速くなるのですか?
ヒープは割り当てまたは割り当て解除に関与するはるかに複雑なブックキーピングを行う一方で、アクセスパターンはメモリの割り当てと割り当て解除を簡単にするため(ポインタ/整数は単純にインクリメントまたはデクリメントされる)、スタックはより高速です。また、スタック内の各バイトは非常に頻繁に再利用される傾向があります。つまり、プロセッサのキャッシュにマップされ、非常に高速になる傾向があります。ヒープのもう1つのパフォーマンスヒットは、通常グローバルリソースであるヒープが通常マルチスレッドセーフでなければならないことです。つまり、各割り当てと割り当て解除は、通常、プログラム内の他の「すべての」ヒープアクセスと同期する必要があります。
明確なデモ:
画像ソース:vikashazrati.wordpress.com
スタック:
ヒープ:
delete
、delete[]
またはfree
。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;
C
言語は、C99
言語標準(open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdfで入手可能)で定義されているように、「スタック」が必要であるというのはよくある誤解です。実際、「スタック」という言葉は規格にさえ出てこない。これは、wrt / to C
のスタックの使用法に関するステートメントに一般的に当てはまりますが、言語によって要求されることはありません。詳細についてはknosof.co.uk/cbook/cbook.htmlを参照してください。特にC
、en.wikipedia.org
最も重要な点は、ヒープとスタックは、メモリを割り当てる方法の一般的な用語であるということです。これらはさまざまな方法で実装でき、これらの用語は基本的な概念に適用されます。
アイテムのスタックでは、アイテムはそこに配置された順に他のアイテムの上に重ねられ、削除できるのは(全体を転倒させることなく)一番上のアイテムのみです。
スタックの単純さは、割り当てられたメモリの各セクションのレコードを含むテーブルを維持する必要がないことです。必要な状態情報は、スタックの最後への単一のポインタだけです。割り当てと割り当て解除を行うには、その単一のポインタをインクリメントおよびデクリメントするだけです。注:スタックは、メモリのセクションの先頭から開始して、上に向かって成長するのではなく、下に拡張するように実装できる場合があります。
ヒープでは、アイテムの配置方法に特定の順序はありません。明確な「トップ」アイテムがないため、任意の順序でアイテムにアクセスして削除できます。
ヒープ割り当てでは、割り当てられているメモリと割り当てられていないメモリの完全な記録を維持する必要があります。また、断片化を減らし、要求されたサイズに十分な大きさの連続したメモリセグメントを見つけるなどのオーバーヘッドの維持も必要です。メモリは、いつでも解放して空き領域を残すことができます。メモリアロケータは、割り当てられたメモリを移動してメモリを最適化したり、ガベージコレクションなどのメンテナンスタスクを実行したりすることがあります。メモリがスコープ内になくなったときに実行時にメモリを識別して割り当てを解除します。
これらのイメージは、スタックとヒープでメモリを割り当てたり解放したりする2つの方法を説明するかなり良い仕事をするはずです。ヤム!
OSまたは言語ランタイムによってどの程度制御されていますか?
前述のように、ヒープとスタックは一般的な用語であり、さまざまな方法で実装できます。コンピュータプログラムには通常、コールスタックと呼ばれるスタックがありますありの関数へのポインタやローカル変数など、現在の関数に関連する情報が格納されています。関数は他の関数を呼び出してから戻るため、スタックは拡大および縮小して、関数からの情報を呼び出しスタックのさらに下に保持します。プログラムは実際にはそれを実行時に制御しません。プログラミング言語、OS、さらにはシステムアーキテクチャによっても異なります。
ヒープは、動的かつランダムに割り当てられるメモリに使用される一般的な用語です。つまり、故障しています。通常、メモリはOSによって割り当てられ、アプリケーションはこの割り当てを行うAPI関数を呼び出します。動的に割り当てられたメモリの管理にはかなりのオーバーヘッドが必要です。これは通常、使用するプログラミング言語または環境のランタイムコードによって処理されます。
それらの範囲は何ですか?
コールスタックは、プログラミングの意味での「スコープ」とは関係がないほど低レベルの概念です。いくつかのコードを逆アセンブルすると、スタックの一部への相対ポインタスタイルの参照が表示されますが、より高レベルの言語に関する限り、言語は独自のスコープのルールを課します。ただし、スタックの重要な側面の1つは、関数が戻ると、その関数にローカルなものはすべてスタックからすぐに解放されることです。これは、プログラミング言語がどのように機能するかを考えると、期待どおりに機能します。ヒープでは、定義することも困難です。スコープはOSによって公開されるものですが、プログラミング言語はおそらく、アプリケーションの「スコープ」が何であるかについてのルールを追加します。プロセッサアーキテクチャとOSは仮想アドレス指定を使用します。これは、プロセッサが物理アドレスに変換し、ページ違反などがあります。どのページがどのアプリケーションに属しているかを追跡します。ただし、プログラミング言語がメモリの割り当てと解放に使用する方法を使用し、エラーをチェックするだけなので、これについて心配する必要はありません(割り当て/解放が何らかの理由で失敗した場合)。
それぞれのサイズを決定するものは何ですか?
繰り返しますが、言語、コンパイラ、オペレーティングシステム、アーキテクチャによって異なります。スタックは通常、事前に割り当てられています。定義上、スタックは連続したメモリでなければならないためです。言語コンパイラまたはOSがサイズを決定します。スタックには大量のデータチャンクを格納しないため、不要な無限再帰(したがって「スタックオーバーフロー」)またはその他の異常なプログラミングの決定の場合を除いて、データが完全に使用されないように十分に大きくなります。
ヒープは、動的に割り当てることができるものの総称です。あなたがそれを見る方法に応じて、それは常にサイズを変えています。現代のプロセッサとオペレーティングシステムでは、それが機能する正確な方法はとにかく非常に抽象化されているので、通常は、それがどのように機能するかについてそれほど心配する必要はありません。まだ割り当てていないか、解放したメモリ。
何が速くなるのですか?
すべての空きメモリが常に隣接しているため、スタックが高速になります。空きメモリのすべてのセグメントのリストを維持する必要はなく、スタックの現在の最上位への単一のポインタだけです。コンパイラは通常、この目的のために、このポインタを特別な高速レジスタに格納します。さらに、スタックに対する後続の操作は通常、メモリの非常に近い領域に集中しており、非常に低いレベルでは、プロセッサのオンダイキャッシュによる最適化に適しています。
(私はこの回答を多かれ少なかれこの問題のだまされた別の質問から移動しました。)
あなたの質問に対する答えは実装固有であり、コンパイラやプロセッサアーキテクチャによって異なる場合があります。ただし、ここでは簡単な説明を示します。
new
またはによるmalloc
)は、空きブロックの1つから適切なブロックを作成することによって満たされます。これには、ヒープ上のブロックのリストを更新する必要があります。ヒープ上のブロックに関するこのメタ情報は、多くの場合、各ブロックの直前の小さな領域のヒープにも格納されます。スタックの代わりに関数をヒープに割り当てることはできますか?
いいえ、関数(つまり、ローカル変数または自動変数)のアクティブ化レコードは、これらの変数を格納するだけでなく、ネストされた関数呼び出しの追跡にも使用されるスタックに割り当てられます。
ヒープの管理方法は、実際にはランタイム環境次第です。Cはmalloc
C ++を使用しますnew
が、他の多くの言語にはガベージコレクションがあります。
ただし、スタックは、プロセッサアーキテクチャに密接に関連する、より低レベルの機能です。十分なスペースがないときにヒープを拡張することは、ヒープを処理するライブラリ呼び出しで実装できるため、それほど難しくありません。ただし、スタックオーバーフローは遅すぎる場合にのみ検出されるため、スタックを拡張することはしばしば不可能です。実行スレッドをシャットダウンすることが唯一の実行可能なオプションです。
次の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
ただし、いくつかの不正確さが含まれている場合があることに注意してください。
スタック 関数を呼び出すと、その関数の引数とその他のオーバーヘッドがスタックに置かれます。一部の情報(戻り先など)もそこに保存されます。関数内で変数を宣言すると、その変数もスタックに割り当てられます。
スタックの割り当て解除は、割り当てと逆の順序で常に割り当て解除するため、非常に簡単です。関数を入力するとスタックが追加され、関数を終了すると対応するデータが削除されます。これは、他の多くの関数を呼び出す多くの関数を呼び出す(または再帰的なソリューションを作成する)場合を除き、スタックの小さな領域にとどまる傾向があることを意味します。
ヒープ ザ・ヒープは、あなたがその場で作成したデータを置く場所の総称です。プログラムが作成する宇宙船の数がわからない場合は、新しい(またはmallocまたは同等の)演算子を使用して各宇宙船を作成する可能性があります。この割り当てはしばらくの間続くため、作成した順序とは異なる順序で解放する可能性があります。
したがって、ヒープははるかに複雑になります。これは、使用されていないチャンクとインターリーブされた未使用のメモリ領域が存在するためです。メモリは断片化されます。必要なサイズの空きメモリを見つけることは難しい問題です。これが、ヒープが使用されない理由です(まだ頻繁に使用されています)。
実装 スタックとヒープの両方の実装は、通常、ランタイム/ OSに依存します。多くの場合、パフォーマンスが重要なゲームやその他のアプリケーションは独自のメモリソリューションを作成し、ヒープから大量のメモリを取得し、OSがメモリに依存するのを避けるために内部的に提供します。
これは、メモリ使用量が標準とはかなり異なる場合にのみ実用的です。つまり、1つの巨大な操作でレベルをロードし、別の巨大な操作で全体をチャックできるゲームの場合のみです。
メモリ内の物理的な場所 これは、物理データが別の場所にある特定のアドレス(ハードディスク上でも!)にアクセスできるとプログラムに思わせる仮想メモリと呼ばれるテクノロジにより、それほど重要ではありません。スタックに対して取得するアドレスは、呼び出しツリーが深くなるにつれて昇順になります。ヒープのアドレスは予測不可能(つまり、実装固有)であり、率直に言って重要ではありません。
明確にするために、この回答には間違った情報が含まれています(トーマスはコメントの後で彼の回答を修正しました、クール:))。他の答えは、静的割り当てが何を意味するかを説明することを避けます。したがって、以下の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
PostScript
は複数のスタックがありますが、スタックのように動作する「ヒープ」があります。
他のものは広いストロークにかなりよく答えたので、私はいくつかの詳細を投げます。
スタックとヒープは単数である必要はありません。複数のスタックがある一般的な状況は、プロセスに複数のスレッドがある場合です。この場合、各スレッドには独自のスタックがあります。また、複数のヒープを使用することもできます。たとえば、一部のDLL構成では、異なるヒープから異なるDLLが割り当てられる可能性があります。そのため、通常、異なるライブラリによって割り当てられたメモリを解放することはお勧めできません。
C では、ヒープに割り当てるallocではなく、スタックに割り当てるallocaを使用することにより、可変長割り当ての利点を得ることができます。このメモリは、returnステートメントを存続させることはできませんが、スクラッチバッファには役立ちます。
Windowsであまり使用しない巨大な一時バッファーを作成することは無料ではありません。これは、関数が入力されるたびに呼び出されるスタックプローブループをコンパイラーが生成して、スタックが存在することを確認するためです(Windowsがスタックの最後にある単一のガードページを使用して、スタックを拡張する必要がある場合を検出するためです。スタックの最後から1ページ以上メモリにアクセスすると、クラッシュします)。例:
void myfunction()
{
char big[10000000];
// Do something that only uses for first 1K of big 99% of the time.
}
alloca
ですか?
他の人があなたの質問に直接回答しましたが、スタックとヒープを理解しようとするときは、従来のUNIXプロセス(スレッドとmmap()
ベースのアロケーターなし)のメモリレイアウトを検討することが役立つと思います。メモリ管理用語集の Webページには、このメモリレイアウトの図があります。
スタックとヒープは従来、プロセスの仮想アドレス空間の両端に配置されています。スタックは、アクセス時にカーネルによって設定されたサイズ(で調整できます)まで自動的に拡大しますsetrlimit(RLIMIT_STACK, ...)
。ヒープは、メモリアロケータがbrk()
やsbrk()
システムコールを呼び出すと大きくなり、物理メモリのより多くのページをプロセスの仮想アドレス空間にマッピングします。
一部の組み込みシステムなど、仮想メモリのないシステムでは、スタックとヒープのサイズが固定されていることを除いて、同じ基本レイアウトがよく適用されます。ただし、他の組み込みシステム(Microchip PICマイクロコントローラーに基づくシステムなど)では、プログラムスタックはメモリの独立したブロックであり、データ移動命令ではアドレス指定できず、プログラムフロー命令(呼び出し、返品など)。Intel Itaniumプロセッサなどの他のアーキテクチャには、複数のスタックがあります。この意味で、スタックはCPUアーキテクチャの要素です。
スタックとは何ですか?
スタックはオブジェクトの山であり、通常は整然と配置されています。
コンピューティングアーキテクチャのスタックは、データが後入れ先出し方式で追加または削除されるメモリ領域です。
マルチスレッドアプリケーションでは、各スレッドに独自のスタックがあります。
ヒープとは何ですか?
ヒープは、無計画に積み上げられた、整頓されたものの集まりです。
コンピューティングアーキテクチャでは、ヒープは動的に割り当てられるメモリの領域であり、オペレーティングシステムまたはメモリマネージャライブラリによって自動的に管理されます。
ヒープ上のメモリは、プログラムの実行中に定期的に割り当て、割り当て解除、およびサイズ変更されます。これにより、断片化と呼ばれる問題が発生する可能性があります。
追加のメモリオブジェクトを保持するには小さすぎるメモリオブジェクトの間に小さなスペースが割り当てられている場合、断片化が発生します。
最終的な結果は、それ以上のメモリ割り当てに使用できないヒープスペースのパーセンテージです。
一緒に
マルチスレッドアプリケーションでは、各スレッドに独自のスタックがあります。ただし、すべての異なるスレッドがヒープを共有します。
異なるスレッドはマルチスレッドアプリケーションでヒープを共有するため、これは、スレッド間でヒープ内の同じメモリにアクセスして操作しないように、スレッド間に何らかの調整が必要であることも意味します。同時に。
スタックとヒープのどちらが速いですか?なぜ?
スタックはヒープよりもはるかに高速です。
これは、メモリがスタックに割り当てられる方法が原因です。
スタックへのメモリの割り当ては、スタックポインタを上に移動するのと同じくらい簡単です。
プログラミングに不慣れな人にとっては、スタックの方が簡単なので、スタックを使用することをお勧めします。
スタックは小さいので、データに必要なメモリの量が正確にわかっている場合、またはデータのサイズが非常に小さい場合に使用します。
データに大量のメモリが必要であることがわかっている場合、または必要なメモリの量がわからない場合(動的配列の場合など)は、ヒープを使用する方が適切です。
スタックは、ローカル変数(メソッドパラメータを含む)が格納されるメモリ領域です。オブジェクト変数に関しては、ヒープ上の実際のオブジェクトへの参照(ポインタ)にすぎません。
オブジェクトがインスタンス化されるたびに、そのオブジェクトのデータ(状態)を保持するためにヒープメモリのチャンクが確保されます。オブジェクトは他のオブジェクトを含むことができるため、このデータの一部は実際にはそれらのネストされたオブジェクトへの参照を保持できます。
スタックは、 'pop'(スタックから値を削除して返す)や 'push'(スタックに値をプッシュする)などのいくつかの主要なアセンブリ言語の命令を介して操作できるメモリの一部ですが、(サブルーチンを呼び出す-これはスタックに戻るためにアドレスをプッシュします)と戻ります(サブルーチンから戻る-これはスタックからアドレスをポップしてそれにジャンプします)。スタックポインタレジスタの下のメモリ領域であり、必要に応じて設定できます。スタックは、サブルーチンに引数を渡すため、およびサブルーチンを呼び出す前にレジスターに値を保持するためにも使用されます。
ヒープは、オペレーティングシステムによってアプリケーションに与えられるメモリの一部であり、通常はmallocのようなsyscallを介して行われます。最近のOSでは、このメモリは呼び出しプロセスのみがアクセスできるページのセットです。
スタックのサイズは実行時に決定され、通常、プログラムの起動後には増加しません。Cプログラムでは、スタックは、各関数内で宣言されたすべての変数を保持するのに十分な大きさである必要があります。ヒープは必要に応じて動的に増加しますが、OSは最終的に呼び出しを行います(多くの場合、ヒープはmallocによって要求された値を超えて増加するため、少なくとも一部の将来のmallocはカーネルに戻って、より多くのメモリを取得します。この動作は多くの場合カスタマイズ可能です)
プログラムを起動する前にスタックを割り当てたので、スタックを使用する前にmallocを実行する必要はないので、そこにはわずかな利点があります。実際には、仮想メモリサブシステムを備えた最新のオペレーティングシステムで何が速く、何が遅くなるかを予測することは非常に困難です。これは、ページの実装方法と保存場所が実装の詳細であるためです。
他の多くの人があなたにこの問題についてほとんど正しい答えを与えたと思います。
しかし、見逃されてきた詳細の1つは、「ヒープ」は実際には「フリーストア」と呼ばれるべきだということです。この区別の理由は、元の無料ストアが「二項ヒープ」と呼ばれるデータ構造で実装されていたためです。そのため、malloc()/ free()の初期の実装からの割り当ては、ヒープからの割り当てでした。ただし、今日のほとんどの無料ストアは、二項ヒープではない非常に精巧なデータ構造で実装されています。
スタックを使用すると、興味深いことができます。たとえば、メモリのヒープではなくスタックを具体的に使用するmallocの形式であるalloca(その使用に関する大量の警告を回避できると想定)のような関数があります。
とはいえ、スタックベースのメモリエラーは私が経験した中で最悪のものの一部です。ヒープメモリを使用していて、割り当てられたブロックの境界を超えると、セグメントフォールトが発生する可能性が高くなります。(100%ではない:ブロックが偶然に以前に割り当てた別のブロックと隣接している場合があります。)しかし、スタック上に作成された変数は常に互いに隣接しているため、境界外に書き込むと、別の変数の値が変わる可能性があります。私のプログラムが論理の法則に従わなくなったと感じるときはいつでも、それはおそらくバッファオーバーフローであることを学びました。
alloca
ですか?たとえば、Windowsで動作しますか?Unixライクなオペレーティングシステム専用ですか?
単純に、スタックはローカル変数が作成される場所です。また、サブルーチンを呼び出すたびに、プログラムカウンター(次の機械語命令へのポインター)とすべての重要なレジスター、そして場合によってはパラメーターがスタックにプッシュされます。次に、サブルーチン内のローカル変数がスタックにプッシュされます(そこから使用されます)。サブルーチンが終了すると、それらすべてがスタックからポップされます。PCとレジスターのデータは、ポップされたときと同じように取得および戻されるので、プログラムを陽気に進めることができます。
ヒープは、動的メモリ割り当てが行われるメモリの領域です(明示的な「新規」または「割り当て」呼び出し)。これは、さまざまなサイズのメモリブロックとその割り当てステータスを追跡できる特別なデータ構造です。
「クラシック」システムでは、RAMは、スタックポインターがメモリの一番下から始まり、ヒープポインターが一番上から始まり、互いに近づくように配置されていました。それらが重なる場合、RAMが不足しています。ただし、最新のマルチスレッドOSでは機能しません。すべてのスレッドには独自のスタックが必要であり、それらは動的に作成されます。
WikiAnwserから。
関数またはメソッドが別の関数を呼び出し、次に別の関数を呼び出すなどの場合、最後の関数がその値を返すまで、それらすべての関数の実行は中断されたままになります。
スタック内の要素(関数呼び出し)は互いに依存しているため、中断された関数呼び出しのこのチェーンがスタックです。
スタックは、例外処理とスレッド実行で考慮することが重要です。
ヒープは、単にプログラムが変数を格納するために使用するメモリです。ヒープの要素(変数)は相互に依存関係がなく、いつでもランダムにアクセスできます。
スタック
ヒープ
わかりました、簡単に、そして短い言葉で言えば、それらは注文されたものであり、注文されていないことを意味します...!
スタック:スタックアイテムでは、物事は互いの上に配置されます。つまり、処理がより高速で効率的になります!...
したがって、特定のアイテムをポイントするインデックスが常にあり、処理も高速になるでしょう。アイテム間にも関係があります!...
ヒープ:順序がなく、処理が遅くなり、値が特定の順序またはインデックスなしで混乱する...ランダムであり、それらの間に関係はない...したがって、実行時間と使用時間は変化する可能性がある...
また、以下の画像を作成して、それらがどのように見えるかを示します。
スタックは静的メモリ割り当てに使用され、ヒープは動的メモリ割り当てに使用されます。どちらもコンピュータのRAMに格納されます。
スタック
スタックは "LIFO"(後入れ先出し)データ構造であり、CPUによって非常に厳密に管理および最適化されます。関数が新しい変数を宣言するたびに、その変数はスタックに「プッシュ」されます。その後、関数が終了するたびに、その関数によってスタックにプッシュされたすべての変数が解放されます(つまり、変数が削除されます)。スタック変数が解放されると、そのメモリ領域は他のスタック変数で使用できるようになります。
スタックを使用して変数を格納する利点は、メモリが管理されることです。手動でメモリを割り当てたり、不要になったメモリを解放したりする必要はありません。さらに、CPUはスタックメモリを非常に効率的に構成するため、スタック変数の読み取りと書き込みは非常に高速です。
詳細については、こちらをご覧ください。
ヒープ
ヒープは、コンピュータのメモリの領域であり、自動的には管理されず、CPUによって厳密に管理されません。これは、メモリのより自由な浮動領域です(より大きくなります)。ヒープにメモリを割り当てるには、組み込みC関数であるmalloc()またはcalloc()を使用する必要があります。ヒープにメモリを割り当てたら、必要がなくなったらfree()を使用してメモリの割り当てを解除する必要があります。
これを行わないと、プログラムにメモリリークと呼ばれるものが発生します。つまり、ヒープ上のメモリは確保されます(他のプロセスでは利用できません)。デバッグのセクションで説明するように、メモリリークの検出に役立つValgrindというツールがあります。
スタックとは異なり、ヒープには可変サイズに関するサイズ制限がありません(コンピューターの明らかな物理的制限は別として)。ヒープ上のメモリにアクセスするにはポインタを使用する必要があるため、ヒープメモリの読み取りと書き込みは少し遅くなります。ポインタについては後で説明します。
スタックとは異なり、ヒープ上に作成された変数は、プログラム内の任意の場所の任意の関数からアクセスできます。ヒープ変数は基本的にスコープがグローバルです。
詳細については、こちらをご覧ください。
スタックに割り当てられた変数はメモリに直接格納され、このメモリへのアクセスは非常に高速で、プログラムのコンパイル時にその割り当てが処理されます。関数またはメソッドが別の関数を呼び出し、次に別の関数を呼び出すなどの場合、最後の関数がその値を返すまで、それらすべての関数の実行は中断されたままになります。スタックは常にLIFOの順序で予約され、最後に予約されたブロックは常に解放される次のブロックです。これにより、スタックの追跡が非常に簡単になり、スタックからブロックを解放することは、1つのポインターを調整することに他なりません。
ヒープに割り当てられた変数には実行時にメモリが割り当てられ、このメモリへのアクセスは少し遅くなりますが、ヒープサイズは仮想メモリのサイズによってのみ制限されます。ヒープの要素は互いに依存関係がなく、いつでもランダムにアクセスできます。ブロックはいつでも割り当て、いつでも解放できます。これにより、ヒープのどの部分が常に割り当てられているか、または解放されているかを追跡することが非常に複雑になります。
スタックは、コンパイル前に割り当てる必要があるデータの量が正確にわかっていて、大きすぎない場合に使用できます。実行時に必要なデータの量が正確にわからない場合、または大量のデータを割り当てる必要がある場合は、ヒープを使用できます。
マルチスレッドの状況では、各スレッドは独自の完全に独立したスタックを持ちますが、ヒープを共有します。スタックはスレッド固有で、ヒープはアプリケーション固有です。スタックは、例外処理とスレッド実行で考慮することが重要です。
各スレッドはスタックを取得しますが、アプリケーションには通常1つのヒープしかありません(ただし、割り当ての種類ごとに複数のヒープがあることは珍しくありません)。
実行時に、アプリケーションがより多くのヒープを必要とする場合、アプリケーションは空きメモリからメモリを割り当てることができ、スタックがメモリを必要とする場合、アプリケーションに割り当てられた空きメモリからメモリを割り当てることができます。
今あなたの質問の答えに来ます。
OSまたは言語ランタイムによってどの程度制御されていますか?
OSは、スレッドが作成されるときに、システムレベルのスレッドごとにスタックを割り当てます。通常、OSは、アプリケーションにヒープを割り当てるために言語ランタイムによって呼び出されます。
詳細については、こちらをご覧ください。
それらの範囲は何ですか?
すでに上位にあります。
「スタックは、コンパイル前に割り当てる必要があるデータの量が正確にわかっていて、それほど大きくない場合に使用できます。実行時に必要なデータの量が正確にわからない場合、または大量のデータを割り当てる必要があります。」
詳細については、こちらをご覧ください。
それぞれのサイズを決定するものは何ですか?
スタックのサイズは、スレッドの作成時にOSによって設定されます。ヒープのサイズはアプリケーションの起動時に設定されますが、スペースが必要になると大きくなる可能性があります(アロケーターはオペレーティングシステムからより多くのメモリを要求します)。
何が速くなるのですか?
スタックの割り当ては、スタックポインタを移動するだけなので、はるかに高速です。メモリプールを使用すると、ヒープの割り当てから同等のパフォーマンスを得ることができますが、それにはわずかに複雑さが追加され、独自の頭痛が伴います。
また、スタックとヒープはパフォーマンスを考慮するだけではありません。また、オブジェクトの予想される寿命についても多くの情報を提供します。
詳細はこちらから。
1980年代に、UNIXは大企業が独自に事業を展開するうさぎのように繁殖しました。エクソンには、歴史に負けた数十のブランド名と同様に1つありました。メモリのレイアウトは、多くの実装者の裁量によるものでした。
典型的なCプログラムは、brk()値を変更することによって増加する機会を伴って、メモリ内でフラットにレイアウトされました。通常、HEAPはこのbrk値のすぐ下にあり、brkを増やすと使用可能なヒープの量が増加しました。
単一のスタックは、通常、HEAPの下の領域であり、次の固定メモリブロックの先頭まで、何も含まれていないメモリ領域です。この次のブロックはしばしばCODEで、その時代の有名なハックの1つでスタックデータによって上書きされる可能性がありました。
典型的なメモリブロックの1つはBSS(ゼロ値のブロック)で、あるメーカーの製品では誤ってゼロ化されていませんでした。もう1つは、文字列や数値などの初期化された値を含むDATAです。3番目は、CRT(Cランタイム)、メイン、関数、およびライブラリを含むコードです。
UNIXでの仮想メモリの出現により、多くの制約が変わります。これらのブロックが連続している、サイズが固定されている、または特定の方法で今すぐ注文する必要があるという客観的な理由はありません。もちろん、UNIX以前はMulticsでしたが、これらの制約を受けませんでした。以下は、その時代のメモリレイアウトの1つを示す概略図です。
数セント:メモリをグラフィカルでよりシンプルに描くのが良いと思います:
矢印-成長スタックとヒープ、プロセススタックサイズに制限がある場所を示します。OSで定義されています。通常、スレッド作成APIのパラメーターによるスレッドスタックサイズの制限です。ヒープは通常、プロセスの最大仮想メモリサイズによって制限されます(たとえば、32ビット2〜4 GB)。
とても簡単な方法:プロセスヒープはプロセスと内部のすべてのスレッドで一般的であり、一般的にmalloc()のようなものでメモリ割り当てに使用します。
スタックは、関数呼び出しのパラメーターとして処理される一般的な関数の戻りポインターと変数を格納するためのクイックメモリであり、ローカル関数変数です。
いくつかの答えがつまらないものになったので、私はダニを寄付します。
驚くべきことに、エキゾチックな言語(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などの言語で非常に人気があります。
主要な点はすでにカバーされていますが、私は何か共有することがあります。
スタック
ヒープ
興味深いメモ:
うわー!非常に多くの答えがあり、そのうちの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
多くの答えは概念として正しいですが、サブルーチン(アセンブリ言語でのCALL)を呼び出すには、ハードウェア(マイクロプロセッサなど)がスタックを必要とすることに注意する必要があります。(OOPの人はそれをメソッドと呼びます)
スタックでは、リターンアドレスを保存し、コール→プッシュ/レット→ポップをハードウェアで直接管理します。
スタックを使用してパラメーターを渡すことができます..それがレジスターを使用するよりも遅い場合でも(マイクロプロセッサーの第一人者か、1980年代のBIOSの優れた本でしょうか...)
スタックの使用は次のように速くなります:
malloc
がカーネル呼び出しだと言うつもりですか?
本当に良い議論をありがとうございましたが、本当の初心者として、指示はどこに保管されているのでしょうか?初めに、科学者は2つのアーキテクチャ(すべてがDATAと見なされるNEUMANNと、メモリ領域が命令用とデータ用に予約されるHARVARDとの間)の間で決定していました。最終的には、フォンノイマンデザインを採用し、すべてが「同じ」と見なされます。アセンブリ やレジスターやスタックポインターについて話しているので、私がアセンブリhttps://www.cs.virginia.edu/~evans/cs216/guides/x86.htmlを学んでいるとき、これは私にとって難しいものでした 。
上記のすべてがDATAについて語っています。私の推測では、命令は特定のメモリフットプリントで定義されたものであるため、スタックに配置され、アセンブリで説明されているすべての「それらの」レジスタはスタックに配置されます。もちろん、動的な構造に命令とデータが混在するオブジェクト指向プログラミングが登場したので、命令もヒープに保持されますか?