したがって、スタックオーバーフローを回避するための一般的なルールとして、大きなオブジェクトをヒープに割り当てる必要があります(間違っている場合は修正してください)。しかし、ヒープとスタックが互いに向かって拡張するので、これはヒープオーバーフローを引き起こしたり、スタックのスペースを制限したりして、スタックオーバーフローの可能性を高めませんか?
明らかに、Xバイトのデータを格納すると、どこに配置しても、空きメモリの量が少なくともXバイト削減されます。
大きなオブジェクトはヒープに割り当てる必要があります
これが主な違いだとは思いません。
現在のスタックフレームの外部から(たとえば、グローバル変数として、または別のスレッドに渡すために)データにアクセスする必要がある場合、そのデータをスタックに入れることはできません。
サブルーチンが戻るとスタックが縮小するため、スタックフレームに格納されているすべてのデータが失われます。
ヒープ上のデータは、割り当てを解除するまで「有効」のままです。
それを超えて、スタックメモリは一般に「実生活」のヒープメモリよりもはるかに制限されているため(質問で言及したように、2つの無制限のセクションが互いに向かって成長しているだけではありません)、ヒープに大きなものを置くこともできます。あなたがそれをスタックに置くことができれば。たとえば、Javaはすべてをヒープに配置します。欠点は、より複雑なメモリ管理です。
以下の前提条件を満たしていれば、はい-床に重ねたり、天井から吊るしたりするかどうかは問題ではありません。
質問は、ヒープまたはスタックのどちらかを選択することを示しています。 (そして、より良いmmapやその他のあまり一般的でないシナリオかもしれない非常に大きなオブジェクトについては話していません)。
仮定
スタックとヒープは同じ固定プロセスメモリ領域にあり、他の制限はありません。
新しい大きなオブジェクトは、ヒープそれぞれのスタックの最後に追加されます。
スタック上のオブジェクトは、関数が戻るときに割り当て解除されます。この選択を本当に対称にするために、ヒープを使用する場合も同様であると想定しています。
これらの仮定を削除するとどうなりますか?
スタックは制限される可能性があります。たとえば、古いOSやマルチスレッドアプリケーションでは、各スレッドが独自のサイズ制限のあるスタックを取得し、スタックに大きなオブジェクトを割り当てるとオーバーフローする可能性があります。一方、ヒープは通常、仮想メモリ管理によってバックアップされます。これにより、プロセスの寿命中にヒープを拡張できます。
通常、ヒープには穴が含まれます。運が良ければ、新しいオブジェクトがその穴の内側に収まる場合は、割り当てを解除されるオブジェクトの残りのスペースが残ります。このような穴は、たとえば、隣接するアドレスに割り当てられた2つ以上の小さなオブジェクトが割り当て解除されたときに開く可能性があります。多くの場合、ヒープを使用した割り当ては、ヒープを拡張することなく満たすことができます。一方、小さなオブジェクトにヒープを使用すると、メモリの断片化を招く可能性があります(Mozillaも参照)、これはアロケータに大きく依存します。
対照的に、スタックに変数を作成すると、同じ量だけスタックが大きくなります。
代わりに、呼び出し元の関数にオブジェクトを返すと仮定します。次に、スタック上の大きなオブジェクトを全体としてコピーする必要があります!代わりに、ヒープのメモリアドレスへのポインタを返す方が、時間と空間の両方でより効率的です。これは、レジスタ内に簡単に収まるので便利です。小さなオブジェクト(整数、浮動小数点数、倍精度数など)は、レジスタ(通常は4または8バイト)または2つに収まることがよくあります。幸いなことに、最近のコンパイラはこのケースを最適化しているので、これについてそれほど心配する必要はありません。
再開:正確な動作はOSとコンパイラに大きく依存しますが、Linuxでは制限がありません。
補遺関連SO質問
スタックオーバーフローは、制限されたスタックを持つアーキテクチャが、スタックポインターを可能な最大値を超えてインクリメントしようとしたときに起こります。これは、メモリの固定位置に256バイトのスタックと1バイトのスタックポインタがあった6502などの場合、ハードリミットになることがあります。 257番目のバイトをスタックにプッシュすると、スタックポインターがラップアラウンドし、スタックの最下部を破壊します。最近のシステムでは、通常、ソフトリミットであり、プログラムを開始する前、またはプログラムの実行中にリセットできます。その行をPOSIX-yシステムでステップオーバーすると、セグメンテーション違反が発生します。理論的には、スタックがメモリを保持できる限り、スタックが減少し続けることも可能です。
あなたが言及している現象はスタックヒープコリジョンと呼ばれ、一方が大きくなりすぎて他方がオーバーラップします。アドレススペース(およびメモリ)が小さいほど、これらはより頻繁に見られました。どちらかがほとんどなくなることはないため、今ではそれほど一般的ではありません。
私のアドバイスは、あなたが記憶を処分する方法に基づいて決定することです。スコープ外に出ると消えてしまう可能性がある場合は、スタックに置くと、管理の手間が省けます。現在のスコープの存続期間を超えて滞在する必要がある場合は、ヒープに配置します。これを適用しない状況もあるので、これを厳格な規則と見なさないでください。判断を下す:スタックを使い果たしてしまうことがわかっているものを割り当てる場合は、ヒープを使用し、自分でクリーンアップするようにしてください。大きな割り当ては再利用される傾向があるため、ヒープに頻繁に配置されます。
肝心なことは、メモリが不足してスタックとヒープが衝突する場合、割り当てたものをどこに置くかよりも大きな問題があるということです。
答えは明らかにプラットフォームに依存し、コンパイラに依存し、さらにはライブラリにも依存します。
ただし、プロセスでは、スレッドごとに1つのスタックがあり、プロセスがシステムに必要な数のヒープがあることを考慮してください(通常は1つですが、それ以上あることもあります。OSは、さまざまな「ヒープ」を割り当てるAPIを持つことができます)。
ヒープとスタックが減算によって取得される特定のチャンクをプロセスに提供するOSと、物理メモリの異なるブロックをそれぞれマッピングするプロセスに個別のブロックを提供するOSがあります。
仮想メモリを実装するOSおよびプロセッサでは、プロセス自体からは見えないいくつかのポインタテーブルによって、プロセスアドレス空間が物理メモリにマップされます。
スタックが大きくなったり小さくなったりすると、「プロセスビュー」で話していることになります。 「OSビュー」では、スタックは、呼び出し/戻りごとに拡大または縮小しない単なるブロックです。それは多かれ少なかれ満たされています。いっぱいになると、より多くのスペースを持つ別の物理メモリ領域に再割り当てする必要があります。 「スタックフレームの再割り当て」にはコストがかかるため(内部に「穴」がないこと)、多くのOSではスタックが事前定義されたサイズを超えて成長することを許可していません。ただし、ヒープにはそのような注意は必要ありません。ヒープブロックがいっぱいになると、別のブロックが追加されます。隣接させる必要はありません。これは(多くのプラットフォームで)ヒープが「無限」(かつ、物理メモリまたはスワップ領域によってのみ制限される)の間にスタックが制限されている(通常は4MBまたは同様)という認識を与えます。
スタックの強みは、LIFOパターンに従うメモリの超高速割り当てと割り当て解除です(最後に割り当てられたものが最初に割り当て解除されます)。異なる割り当て/割り当て解除パターンに従うメモリが必要な場合は、ヒープから取得する必要があります。
一般的なマルチプロセッシングオペレーティングシステムでは、すべてのプロセスが独自のスタックと独自のヒープを取得することに注意してください。通常、プロセスのスタックはわずか1MBから5MBです。サイズが小さいため、スタックに大きな構造体を割り当てないようにする必要があります。対照的に、ヒープはOSが許可する限りの物理メモリを消費するように成長する可能性があります。はい、最終的にOSが許可するすべてのヒープを使い果たす可能性がありますが、これは通常、スタックのサイズの1000倍の係数になります。
これはあなたが心配する必要のないものです。ヒープとスタックは別々に管理されます。より具体的には、ヒープは仮想メモリによってバックアップされるため、それを全体的に割り当てると、OSがページアウトします。スタックは、通常、特定のサイズを超えて拡張しない別個のものです。ヒープから自由に割り当てられません。
彼らがヒープから大きなものを割り当てると言う理由は、ヒープはそれを処理でき、スタックは固定サイズでは処理できないためです。
スタックをさらに詳しく見ると、スタックが永遠に、またはメモリが使い果たされるまで成長し続けると考えるかもしれませんが、それは正しいことです。ただし、ほとんど/すべてのOSがスタックのサイズを監視しており、スタックが継続的に成長するのを防ぎます。
最後に、ヒープからデータを割り当てるとき、それは管理データです。つまり、OSは必要なときにデータが割り当てられ、割り当てが解除されるということです。ヒープが不足すると、mallocはNULLを返します。ほとんどのOSでは、仮想メモリがこれをバックアップするため、mallocは常に何かを返すことに注意してください。