プログラミング言語の本では、値の型は stack の上に作成され、参照型は heap の上に作成されると説明していますが、これら2つのことは説明しません。私はこれについての明確な説明を読んでいません。 スタック が何であるかがわかります。しかし、
スタックは、実行スレッド用のスクラッチスペースとして確保されているメモリです。関数が呼び出されると、ローカル変数といくつかの簿記データのためにブロックがスタックの最上部に予約されます。その関数が戻ると、ブロックは未使用になり、次に関数が呼び出されたときに使用できるようになります。スタックは常にLIFO(後入れ先出し)の順序で予約されています。最後に予約されたブロックは常に解放される次のブロックです。これにより、スタックを追跡することが非常に簡単になります。スタックからブロックを解放することは、1つのポインタを調整することに他なりません。
ヒープは動的割り当て用に確保されたメモリです。スタックとは異なり、ヒープからのブロックの割り当てと割り当て解除には強制的なパターンはありません。あなたはいつでもブロックを割り当て、いつでもそれを解放することができます。これにより、いつでもヒープのどの部分が割り当てられているか、または解放されているかを追跡することがはるかに複雑になります。さまざまな使用パターンに合わせてヒープのパフォーマンスを調整するために使用できるカスタムヒープアロケータは多数あります。
各スレッドはスタックを取得しますが、通常はアプリケーションに対して1つのヒープしかありません(ただし、異なるタイプの割り当てに対して複数のヒープがあることは珍しくありません)。
直接質問に答えるには:
OSや言語のランタイムによってどの程度制御されていますか?
スレッドが作成されると、OSは各システムレベルスレッドにスタックを割り当てます。通常、OSは言語ランタイムによって呼び出され、アプリケーションにヒープを割り当てます。
彼らの範囲は?
スタックはスレッドにアタッチされているため、スレッドがスタックを終了するときには再利用されます。ヒープは通常、ランタイムによるアプリケーションの起動時に割り当てられ、アプリケーション(技術的にはプロセス)の終了時に回収されます。
それぞれの大きさを決めるものは?
スタックのサイズは、スレッドが作成されたときに設定されます。ヒープのサイズはアプリケーションの起動時に設定されますが、スペースが必要になると大きくなる可能性があります(アロケータはオペレーティングシステムからより多くのメモリを要求します)。
何が速くなるのですか?
アクセスパターンによってメモリの割り当てと割り当て解除が簡単になるため(ポインタ/整数は単に増分または減分される)、ヒープは割り当てまたは割り当て解除に関連するはるかに複雑な簿記を持つため、スタックは高速です。また、スタック内の各バイトは非常に頻繁に再利用される傾向があります。つまり、プロセッサのキャッシュにマップされる傾向があり、非常に高速になります。ヒープに対する別の性能上の打撃は、大部分がグローバルリソースであるヒープは、通常マルチスレッドセーフでなければならない、すなわち、各割り当ておよび割り当て解除は、プログラム内の他のすべてのヒープアクセスと同期する必要があることである。
明確なデモンストレーション:
画像ソース: 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;
最も重要な点は、ヒープとスタックはメモリを割り当てる方法の総称です。それらはさまざまな方法で実装でき、その用語は基本概念にも適用されます。
アイテムのスタックでは、アイテムはそこに配置されている順序で上下に並んでおり、一番上のアイテムだけを削除できます(全体をひっくり返すことなく)。
スタックの単純さは、割り当てられたメモリの各セクションのレコードを含むテーブルを管理する必要がないということです。あなたが必要とする唯一の状態情報はスタックの終わりへの単一のポインタです。割り当ておよび割り当て解除するには、その単一のポインタを増減します。注:スタックは、メモリーのセクションの先頭から始まり、上方向に成長するのではなく下方向に延びるように実装されることがあります。
ヒープ内では、アイテムの配置方法に特別な順序はありません。明確な「最上位」の項目がないため、任意の順序で到達して項目を削除できます。
ヒープ割り当てでは、割り当てられているメモリと割り当てられていないメモリの完全な記録を維持し、断片化を減らしたり、要求されたサイズに合う大きさの連続したメモリセグメントを見つけるためのオーバーヘッドメンテナンスなどが必要です。空き領域を残して、メモリの割り当てをいつでも解除できます。メモリアロケータは、割り当てられたメモリを移動したり、ガベージコレクションを実行してメモリの断片化を解消するなど、メンテナンスタスクを実行することがあります。
これらのイメージは、スタックとヒープ内のメモリの割り当てと解放の2つの方法を説明するためのかなり優れた仕事をするはずです。ヤム!
OSや言語のランタイムによってどの程度管理されていますか。
前述のように、ヒープとスタックは一般的な用語であり、さまざまな方法で実装できます。コンピュータプログラムは通常、 呼び出しスタック と呼ばれるスタックを持っています。これには、呼び出し元の関数へのポインタやローカル変数など、現在の関数に関連する情報が格納されています。関数は他の関数を呼び出してから戻るため、呼び出しスタックのさらに下にある関数からの情報を保持するためにスタックが増減します。プログラムは実際にはそれをランタイム制御できません。それはプログラミング言語、OSそしてシステムアーキテクチャによってさえ決定されます。
ヒープとは、動的にランダムに割り当てられるメモリに使用される一般的な用語です。すなわち、故障しています。通常、メモリはOSによって割り当てられ、アプリケーションはこの割り当てを行うためにAPI関数を呼び出します。動的に割り当てられたメモリの管理には、かなりのオーバーヘッドが必要です。これは通常、OSによって処理されます。
彼らの範囲は何ですか?
呼び出しスタックは、プログラミングという意味での「スコープ」とは関係がないという、非常に低レベルの概念です。コードを逆アセンブルすると、スタックの一部への相対ポインタスタイルの参照が表示されますが、より高レベルの言語に関する限り、その言語は独自のスコープ規則を課します。ただし、スタックの重要な側面の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
そしてここ:
この記事は、上の図のソースです。 6つの重要な.NETの概念:スタック、ヒープ、値型、参照型、ボクシング、およびボックス化解除 - CodeProject
しかし、それはいくつかの不正確さを含むかもしれないことに注意してください。
スタック 関数を呼び出すと、その関数への引数とその他のオーバーヘッドがスタックに追加されます。いくつかの情報(どこに戻るかなど)もそこに格納されています。関数の中で変数を宣言すると、その変数もスタックに割り当てられます。
スタックの割り当てを解除するのは非常に簡単です。割り当てた順序とは逆の順序で割り当てを解除するからです。関数を入力するとスタック関連のものが追加され、終了すると対応するデータが削除されます。つまり、他の多くの関数を呼び出す(または再帰的な解決策を作成する)多くの関数を呼び出さない限り、スタックの小さな領域内に留まる傾向があります。
ヒープ ヒープは、作成したデータをその場で置く場所の総称です。プログラムが作成するスペースシップの数がわからない場合は、新しい(またはmallocまたは同等の)演算子を使用して各スペースシップを作成します。この割り当てはしばらくの間続きますので、作成した順序とは異なる順序で解放する可能性があります。
したがって、ヒープははるかに複雑になります。未使用のメモリ領域が、チャンクと交互に配置されるためです。メモリが断片化します。必要なサイズの空きメモリを見つけるのは難しい問題です。ヒープは避けなければならないのはこのためです(まだ使用されていることが多いですが)。
実装 スタックとヒープの両方の実装は通常ランタイム/ OSに任されています。パフォーマンスを重視するゲームやその他のアプリケーションは、ヒープから大量のメモリを取得し、メモリをOSに依存しないように内部的に調整する独自のメモリソリューションを作成することがよくあります。
これは、あなたのメモリ使用量が通常とはかなり異なる場合にのみ実用的です - すなわち、あなたが1つの巨大な操作でレベルをロードして、別の巨大な操作でたくさん丸ごと逃げることができるゲームの場合。
メモリ内の物理的な場所 これは Virtual Memory と呼ばれるテクノロジのため、プログラムが特定のアドレスにアクセスできると判断するため、あまり意味がありません。物理データは他の場所にあります(ハードディスク上にさえあります)。コールツリーが深くなるにつれて、スタック用に取得したアドレスは昇順になります。ヒープのアドレスは予測不可能であり(つまり、暗黙的に固有)、率直に言って重要ではありません。
はっきりさせるために、 この答え は間違った情報を持っています( thomas コメントの後に答えを直しました、クール:))。他の答えは、静的割り当てが何を意味するのか説明するのを避けるだけです。そこで、3つの主な割り当て形式と、それらが通常ヒープ、スタック、およびデータセグメントにどのように関連するかを以下で説明します。私はまた人々が理解するのを助けるためにC/C++とPythonの両方でいくつかの例を示すでしょう。
"静的"(AKA静的割り当て)変数はスタックに割り当てられません。そうとは思わないでください - 多くの人が「静的」が「スタック」に非常に似ているという理由だけでそうします。それらは実際にはスタックにもヒープにも存在しません。これらは データセグメント と呼ばれるものの一部です。
しかし、一般的には、 "stack"や "heap"よりも "scope"や "lifetime"を検討するほうが良いでしょう。
スコープは、コードのどの部分が変数にアクセスできるかを表します。一般的に、local scope(現在の関数からしかアクセスできない)とglobal scope(どこからでもアクセスできます)を考えることができますが、スコープはもっと複雑になる可能性があります。
有効期間とは、プログラムの実行中に変数が割り当てられたり割り当て解除されたりする時期を指します。通常、static allocation(変数はプログラムの全期間を通じて持続するため、複数の関数呼び出しにわたって同じ情報を格納するのに役立ちます)対automatic allocation(変数は1回の呼び出しでのみ持続します) dynamic allocation(実行時に期間が定義されている変数で、静的または自動のようなコンパイル時ではなく)関数内でのみ使用され、実行後に破棄される可能性のある情報を格納するのに役立ちます。 ).
ほとんどのコンパイラやインタプリタはスタックやヒープなどを使うという点ではこの振る舞いを同じように実装していますが、振る舞いが正しい限り望むならばコンパイラは時々これらの規約を破るかもしれません。たとえば、最適化により、ローカル変数は、ほとんどのローカル変数がスタック内に存在していても、レジスタ内にのみ存在するか、完全に削除される可能性があります。いくつかのコメントで指摘されているように、スタックやヒープさえも使用しないコンパイラを自由に実装することができますが、代わりに他のストレージメカニズムを使用することはほとんどありません。
これらすべてを説明するために、簡単な注釈付き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.
有効期間と有効範囲を区別することが重要である理由の特に気になる例は、変数がローカル有効範囲を持つことができるが静的有効期間を持つことができるということです。そのような変数は私たちの共通だが非公式の命名規則を非常に混乱させる可能性があります。たとえば、 "local"と言うときは通常 "ローカルスコープの自動割り当て変数"を意味し、 "global"と言うときは "グローバルスコープの静的割り当て変数 "残念ながら、 "ファイルスコープの静的に割り当てられた変数"のようなものになると多くの人が言うだけで... "ハァッ??"。
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つのコードファイルからしかアクセスできない変数を表すために "static"または "static scope"というフレーズを使用します。生涯の文脈では、 "static" alwaysは、変数がプログラムの開始時に割り当てられ、プログラムの終了時に割り当て解除されることを意味します。
一部の人々はこれらの概念を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 __== "__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
他の人たちは広いストロークにかなりよく答えているので、私はいくつかの詳細を投げます。
スタックとヒープは単数である必要はありません。複数のスタックがある一般的な状況は、プロセスに複数のスレッドがある場合です。この場合、各スレッドは独自のスタックを持ちます。複数のヒープを持つこともできます。たとえば、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.
}
他の人があなたの質問に直接答えましたが、スタックとヒープを理解しようとするとき、私は伝統的なUNIXプロセスのメモリレイアウトを考慮することが役に立つと思います(スレッドとmmap()
ベースのアロケータなしで)。 メモリ管理用語集 Webページに、このメモリレイアウトの図があります。
スタックとヒープは伝統的にプロセスの仮想アドレス空間の反対側にあります。スタックはアクセスされると自動的にカーネルによって設定されたサイズ(setrlimit(RLIMIT_STACK, ...)
で調整可能)まで成長します。ヒープは、メモリアロケータがbrk()
またはsbrk()
システムコールを呼び出し、物理メモリのより多くのページをプロセスの仮想アドレス空間にマッピングするときに大きくなります。
一部の組み込みシステムなど、仮想メモリのないシステムでは、スタックとヒープのサイズが固定されていることを除けば、同じ基本レイアウトがよく適用されます。しかし、他の組み込みシステム(Microchip PICマイクロコントローラをベースにしたものなど)では、プログラムスタックはデータ移動命令でアドレス指定できない独立したメモリブロックであり、プログラムフロー命令を介して間接的にしか変更または読み取ることができません。戻るなど) Intel Itaniumプロセッサなどの他のアーキテクチャでは、 複数スタック があります。この意味で、スタックはCPUアーキテクチャの要素です。
スタックは、 'pop'(スタックから値を削除して返す)や 'Push'(スタックに値をプッシュする)など、いくつかの重要なアセンブリ言語命令を介して操作できるメモリの一部です。サブルーチンを呼び出す - これはスタックに戻るためにアドレスをプッシュする)そして戻る(サブルーチンから戻る - これはスタックからアドレスをポップしてそれにジャンプする)。これはスタックポインタレジスタの下のメモリ領域で、必要に応じて設定できます。スタックは、サブルーチンに引数を渡すためにも、サブルーチンを呼び出す前にレジスタ内の値を保存するためにも使用されます。
ヒープは、通常mallocのようなシステムコールを通して、オペレーティングシステムによってアプリケーションに与えられるメモリの一部です。最近のOSでは、このメモリは呼び出しプロセスだけがアクセスできるページのセットです。
スタックのサイズは実行時に決定され、通常はプログラムの起動後に大きくなることはありません。 Cプログラムでは、スタックは各関数内で宣言されたすべての変数を保持するのに十分な大きさである必要があります。ヒープは必要に応じて動的に大きくなりますが、OSは最終的に呼び出しを行います(多くの場合、mallocで要求された値を超えてヒープが大きくなるため、少なくとも将来のmallocはカーネルに戻ってもより多くのメモリを手に入れよう。
プログラムを起動する前にスタックを割り当てているので、スタックを使用する前にmallocを実行する必要はありません。これは、そこでのわずかな利点です。実際には、仮想メモリサブシステムを搭載した最新のオペレーティングシステムで何が速くなり、何が遅くなるかを予測するのは非常に困難です。ページの実装方法と格納場所が実装の詳細だからです。
私は他の多くの人々がこの問題に関してあなたに主に正しい答えを与えたと思います。
しかし、見逃されてきた1つの詳細は、「ヒープ」は実際にはおそらく「フリーストア」と呼ばれるべきであるということです。この区別の理由は、元のフリーストアが「二項ヒープ」として知られるデータ構造で実装されていることです。そのため、malloc()/ free()の初期の実装からの割り当てはヒープからの割り当てでした。しかし、今日のほとんどのフリーストアは、2項式のヒープではない非常に複雑なデータ構造で実装されています。
スタックとは
スタックはオブジェクトの山で、通常はきれいに配置されています。
コンピューティングアーキテクチャのスタックは、後入れ先出し法でデータが追加または削除されるメモリ領域です。
マルチスレッドアプリケーションでは、各スレッドは独自のスタックを持ちます。
ヒープとは何ですか?
ヒープは、不器用に積み重ねられたものの乱雑なコレクションです。
コンピューティングアーキテクチャでは、ヒープは、オペレーティングシステムまたはメモリマネージャライブラリによって自動的に管理される、動的に割り当てられたメモリの領域です。
ヒープ上のメモリは、プログラムの実行中に定期的に割り当てられ、割り当て解除され、サイズ変更されます。これにより、フラグメンテーションと呼ばれる問題が発生する可能性があります。
断片化は、メモリオブジェクトが追加のメモリオブジェクトを保持するには小さすぎる間の小さなスペースで割り当てられると発生します。
最終的な結果は、それ以上のメモリ割り当てに使用できないヒープスペースの割合です。
両方一緒に
マルチスレッドアプリケーションでは、各スレッドは独自のスタックを持ちます。しかし、すべての異なるスレッドがヒープを共有します。
マルチスレッドアプリケーションでは、異なるスレッドがヒープを共有するため、スレッド間で何らかの調整が必要で、スレッドが同じ部分にアクセスして操作しようとしないようにする必要があります。同時にヒープ内のメモリ。
スタックとヒープのどちらが速いですか。なぜ?
スタックはヒープよりはるかに高速です。
これは、メモリがスタックに割り当てられる方法が原因です。
スタックへのメモリの割り当ては、スタックポインタを上に動かすのと同じくらい簡単です。
プログラミングに慣れていない人にとっては、スタックの方が簡単なのでおそらく使用することをお勧めします。
スタックは小さいので、データに必要なメモリ量が正確にわかっているとき、またはデータのサイズが非常に小さいことがわかっているときに使用します。
データ用に大量のメモリが必要になることがわかっている場合、または動的メモリの場合のようにどれだけのメモリが必要かわからない場合は、ヒープを使用する方が適切です。
スタックは、ローカル変数(メソッドパラメータを含む)が格納されているメモリ領域です。オブジェクト変数に関しては、これらは単にヒープ上の実際のオブジェクトへの参照(ポインタ)にすぎません。
オブジェクトがインスタンス化されるたびに、そのオブジェクトのデータ(状態)を保持するためにヒープメモリのチャンクが確保されます。オブジェクトは他のオブジェクトを含むことができるので、このデータの一部は実際にはそれらのネストされたオブジェクトへの参照を保持できます。
あなたはスタックでいくつかの興味深いことをすることができます。たとえば、 alloca のような関数を使用している場合(その使用方法に関する多数の警告を回避できると仮定します)、これはメモリのためにヒープではなくスタックを特に使用するmallocの形式です。
とは言っても、スタックベースのメモリエラーは私が経験した中で最悪のものです。ヒープメモリを使用していて、割り当てられたブロックの境界を超えた場合は、セグメントエラーが発生する可能性が高くなります。ただし、スタック上に作成された変数は常に隣接しているため、範囲外に書き込むと別の変数の値が変わる可能性があります。私のプログラムが論理の法則に従わなくなったと感じるときはいつでも、それはおそらくバッファオーバーフローであることを私は学びました。
簡単に言うと、スタックはローカル変数が作成される場所です。また、サブルーチンを呼び出すたびに、プログラムカウンタ(次の機械語命令へのポインタ)と重要なレジスタ、そして時にはパラメータがスタックにプッシュされます。その後、サブルーチン内のローカル変数はスタックにプッシュされます(そしてそこから使用されます)。サブルーチンが終了すると、そのすべてがスタックからポップされます。 PCとレジスターのデータは、ポップされたときの状態で取得され、元の場所に戻されます。そのため、あなたのプログラムは賢い方法で進むことができます。
ヒープは、動的メモリ割り当てが行われるメモリの領域です(明示的な「new」または「allocate」呼び出し)。これは、さまざまなサイズのメモリブロックとその割り当てステータスを追跡できる特別なデータ構造です。
"古典的な"システムではRAMはスタックポインタがメモリの一番下から始まり、ヒープポインタが一番上から始まりお互いに向かって大きくなるようにレイアウトされていました。重なっている場合は、RAMが不足しています。ただし、最近のマルチスレッドOSではうまくいきません。すべてのスレッドは独自のスタックを持つ必要があり、それらは動的に作成されます。
WikiAnwserから。
関数またはメソッドが別の関数を呼び出し、その関数が別の関数などを呼び出すと、最後の関数がその値を返すまで、それらすべての関数の実行は中断されたままになります。
スタック内の要素(関数呼び出し)は互いに依存しているため、この一連の中断された関数呼び出しはスタックです。
スタックは、例外処理とスレッド実行で考慮することが重要です。
ヒープは単に変数を格納するためにプログラムによって使用されるメモリです。ヒープの要素(変数)は互いに依存関係がなく、いつでもランダムにアクセスできます。
スタック
ヒープ
スタックは静的メモリ割り当てに使用され、ヒープは動的メモリ割り当てに使用され、両方ともコンピュータのRAMに格納されます。
スタック
スタックは "LIFO"(後入れ先出し)データ構造で、CPUによって非常に厳密に管理および最適化されています。関数が新しい変数を宣言するたびに、それはスタックに「プッシュ」されます。その後、関数が終了するたびに、その関数によってスタックにプッシュされたすべての変数が解放されます(つまり、それらは削除されます)。スタック変数が解放されると、そのメモリ領域は他のスタック変数で使用可能になります。
スタックを使用して変数を格納する利点は、メモリが管理されることです。手動でメモリを割り当てたり、いらなくなったら解放したりする必要はありません。さらに、CPUはスタックメモリを非常に効率的に編成するため、スタック変数の読み書きは非常に高速です。
もっと見つけることができますここ。
ヒープ
ヒープはあなたのために自動的に管理されていないあなたのコンピュータのメモリの領域であり、そしてCPUによって厳密に管理されていません。それはメモリのより自由に浮遊する領域です(そしてより大きいです)。ヒープ上にメモリを割り当てるには、組み込みC関数であるmalloc()またはcalloc()を使用する必要があります。ヒープ上にメモリを割り当てた後は、必要がなくなったときにfree()を使用してそのメモリを解放する必要があります。
これを怠ると、プログラムはメモリリークとして知られるものを持つことになります。つまり、ヒープ上のメモリはまだ確保されています(そして他のプロセスで利用できなくなります)。デバッグのセクションで見るように、 Valgrind と呼ばれるツールがあり、これはあなたがメモリリークを検出するのを助けることができます。
スタックとは異なり、ヒープは可変サイズにサイズ制限がありません(お使いのコンピュータの明らかな物理的制限は別として)。ヒープメモリは、ヒープ上のメモリにアクセスするためにポインタを使用しなければならないため、読み書きが少し遅くなります。ポインタについてはすぐに話します。
スタックとは異なり、ヒープ上に作成された変数は、プログラム内の任意の場所で、あらゆる関数からアクセスできます。ヒープ変数は、本質的にスコープがグローバルです。
もっと見つけることができますここ。
スタックに割り当てられた変数は直接メモリに格納され、このメモリへのアクセスは非常に高速であり、その割り当てはプログラムのコンパイル時に処理されます。関数またはメソッドが別の関数を呼び出し、その関数が別の関数などを呼び出すと、最後の関数がその値を返すまで、それらすべての関数の実行は中断されたままになります。スタックは常にLIFOの順序で予約され、最後に予約されたブロックは常に解放される次のブロックです。これにより、スタックを追跡することが非常に簡単になります。スタックからブロックを解放することは、1つのポインタを調整することに他なりません。
ヒープに割り当てられた変数は実行時に割り当てられたメモリを持ち、このメモリへのアクセスは少し遅くなりますが、ヒープサイズは仮想メモリのサイズによってのみ制限されます。ヒープの要素は互いに依存関係がなく、いつでもランダムにアクセスできます。ブロックはいつでも割り当てて、いつでも解放できます。これにより、いつでもヒープのどの部分が割り当てられているか、または解放されているかを追跡することがはるかに複雑になります。
コンパイル時までにどれだけの量のデータを割り当てる必要があるかがわかっていれば、スタックを使用できますが、それほど大きくはありません。実行時に必要なデータ量が正確にわからない場合や、大量のデータを割り当てる必要がある場合は、ヒープを使用できます。
マルチスレッドの状況では、各スレッドは独自の完全に独立したスタックを持ちますが、それらはヒープを共有します。スタックはスレッド固有であり、ヒープはアプリケーション固有です。スタックは、例外処理とスレッド実行で考慮することが重要です。
各スレッドはスタックを取得しますが、通常はアプリケーションに対して1つのヒープしかありません(ただし、異なるタイプの割り当てに対して複数のヒープがあることは珍しくありません)。
実行時に、アプリケーションがより多くのヒープを必要とする場合は空きメモリからメモリを割り当てることができ、スタックがメモリを必要とする場合はアプリケーション用に割り当てられた空きメモリからメモリを割り当てることができます。
あなたの質問の答え に来てください。
OSや言語のランタイムによってどの程度制御されていますか?
スレッドが作成されると、OSは各システムレベルスレッドにスタックを割り当てます。通常、OSは言語ランタイムによって呼び出され、アプリケーションにヒープを割り当てます。
もっと見つけることができますここ。
彼らの範囲は?
すでに上で与えられています。
「コンパイル時までにどれだけの量のデータを割り当てる必要があるのか、それほど大きくない場合はスタックを使用できます。実行時に必要なデータの量が正確にわからない場合、大量のデータを割り当てる必要があります。」
here でもっと見つけることができます。
それぞれの大きさを決めるものは?
スタックのサイズは、スレッドの作成時に _ os _ によって設定されます。ヒープのサイズはアプリケーションの起動時に設定されますが、スペースが必要になると大きくなる可能性があります(アロケータはオペレーティングシステムからより多くのメモリを要求します)。
何が速くなるのですか?
スタック割り当ては、スタックポインタを移動するだけなので、はるかに高速です。メモリプールを使用すると、ヒープ割り当てから匹敵するパフォーマンスを得ることができますが、それには少し複雑さと独自の頭痛が伴います。
また、スタック対ヒープはパフォーマンス上の考慮事項だけではありません。また、オブジェクトの予想寿命についても多くのことを伝えています。
詳細はhereから見つけることができます。
1980年代には、UNIXは大企業が自分たちのものを転がしてバニーのように繁殖しました。エクソンには数十ものブランド名が歴史に消えていたようなものがありました。実装者。
典型的なCプログラムは、brk()の値を変更することによって増加する機会を持ってフラットにメモリにレイアウトされていました。典型的に、HEAPはこのbrk値のすぐ下で brkは利用可能なヒープの量を増やしました。
単一のSTACKは通常HEAPの下の領域であり、次の固定されたメモリブロックの先頭まで値の何も含まれていないメモリの領域でした。その時代の有名なハックの1つでスタックデータによって。
1つの典型的なメモリブロックはBSS(ゼロ値のブロック)であり、これはあるメーカーの製品では偶然ゼロにされませんでした。文字列や数字を含む初期化値を含むDATAです。 3番目は、CRT(Cランタイム)、main、関数、およびライブラリーを含むCODEでした。
UNIXでの仮想メモリの出現は多くの制約を変えます。これらのブロックを連続させる必要がある、[。]、サイズを固定する、または特定の順序で順序付ける必要があるという客観的な理由はありません。もちろん、UNIXがMulticsになる前は、これらの制約に悩まされていませんでした。これは、その時代のメモリレイアウトの1つを示す概略図です。
数セント:私は、メモリをグラフィカルに、そしてもっと単純に描くのが良いと思います。
矢印 - 通常はスタックとヒープの拡大、プロセススタックサイズには制限があることを示します。OSで定義されています。スレッドスタックサイズはスレッド作成APIのパラメータによって制限されます。ヒープは通常、プロセスごとに最大仮想メモリサイズを制限します(たとえば32ビット2〜4 GB)。
とても簡単な方法:プロセスヒープはプロセスとその中のすべてのスレッドに対して一般的で、 malloc() のような一般的なケースではメモリ割り当てに使用されます。
スタックは、一般的な場合に格納するためのクイックメモリです。関数戻りポインタと変数は、関数呼び出しのパラメータ、ローカル関数変数として処理されます。
いくつかの答えが徹底的に調べられたので、私はダニに貢献するつもりです。
驚くべきことに、複数の(つまり、実行中のOSレベルのスレッドの数とは無関係の)呼び出しスタックが、異国語(PostScript)やプラットフォーム(Intel Itanium)だけでなく にもあることに言及していないfiber 、 緑色のスレッド 、および coroutines のいくつかの実装。
繊維、緑色の糸、コルーチンは、多くの点で似ているため、混乱を招きます。ファイバーとグリーンスレッドの違いは、前者が協調的マルチタスクを使用しているのに対し、後者は協調的またはプリエンプティブのいずれか(またはその両方)を特徴としていることです。ファイバーとコルーチンの違いについては、 here を参照してください。
いずれにせよ、ファイバー、グリーンスレッド、コルーチンの両方の目的は、同時に実行される複数の関数を持つことですが、ではありません(区別については this SO question を参照)単一のOSレベルのスレッド内で、体系的に相互に制御をやり取りします。
繊維、緑色の糸、コルーチンを使うとき、あなたは通常関数ごとに別々のスタックを持っています。 (技術的には、スタックだけではなく、実行のコンテキスト全体が関数ごとにあります。最も重要なのは、CPUレジスタです。)すべてのスレッドに対して、同時に実行される関数と同じ数のスタックがあり、スレッドは各関数の実行を切り替えます。あなたのプログラムの論理に従って。関数が最後まで実行されると、そのスタックは破棄されます。そのため、 スタックの数と有効期間 は動的であり、 はOSレベルのスレッドの数によって決まるわけではありません。
私は "通常は関数ごとに別々のスタックを持っている"と言ったことに注意してください。クールアウトには、stackfulとstacklessの両方の実装があります。最も注目すべきスタックフルC++の実装は Boost.Coroutine と Microsoft PPL のasync/await
です。 (ただし、C++ 17に提案されたC++の resumable関数 (a.k.a. "async
およびawait
")は、スタックレスコルーチンを使用する可能性があります。)
C++標準ライブラリへのファイバの提案が間近です。また、サードパーティの libraries もあります。グリーンスレッドはPythonやRubyのような言語で非常に人気があります。
重要な点についてはすでに説明しましたが、共有することがあります。
スタック
ヒープ
興味深いことに:
概念としては多くの答えが正しいが、サブルーチンを呼び出すことを可能にするためにハードウェア(すなわちマイクロプロセッサ)がスタックを必要とすることに注意しなければならない(アセンブリ言語ではCALL ...)。 (OOPの人たちはそれを methods と呼ぶでしょう)
スタック上にリターンアドレスを保存し、→プッシュ/リトリーブ→ポップの呼び出しをハードウェアで直接管理します。
スタックを使用してパラメータを渡すことができます。たとえそれがレジスタを使用するより遅いとしても(マイクロプロセッサの第一人者か1980年代の良いBIOSの本と言うでしょう...)
スタックの使用は以下のように高速です。
うわー!非常に多くの回答がありますが、そのうちの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/ja-jp/windows/desktop/api/heapapi/nf-heapapi-getprocessheap
https://docs.Microsoft.com/ja-jp/windows/desktop/api/heapapi/nf-heapapi-heapcreate