回答:
いいえ、スタックとヒープの違いはパフォーマンスではありません。それは寿命です。関数内のローカル変数(malloc()やnew以外のもの)はスタックに存在します。関数から戻ると消えます。宣言した関数よりも長く存続させたい場合は、ヒープに割り当てる必要があります。
class Thingy;
Thingy* foo( )
{
int a; // this int lives on the stack
Thingy B; // this thingy lives on the stack and will be deleted when we return from foo
Thingy *pointerToB = &B; // this points to an address on the stack
Thingy *pointerToC = new Thingy(); // this makes a Thingy on the heap.
// pointerToC contains its address.
// this is safe: C lives on the heap and outlives foo().
// Whoever you pass this to must remember to delete it!
return pointerToC;
// this is NOT SAFE: B lives on the stack and will be deleted when foo() returns.
// whoever uses this returned pointer will probably cause a crash!
return pointerToB;
}
スタックが何であるかをより明確に理解するには、高レベル言語の観点からスタックの機能を理解しようとするのではなく、「コールスタック」と「呼び出し規約」を調べて何を参照するかを検討してください。関数を呼び出すと、マシンは実際に実行します。コンピュータのメモリは単なる一連のアドレスです。「ヒープ」と「スタック」はコンパイラの発明です。
私は言うでしょう:
できればスタックに格納します。
必要な場合は、ヒープに保管してください。
したがって、ヒープよりもスタックを優先します。スタックに何かを格納できないいくつかの考えられる理由は次のとおりです。
賢明なコンパイラを使用すると、ヒープに固定されていないサイズのオブジェクト(通常、コンパイル時にサイズがわからない配列)を割り当てることができます。
他の回答が示唆するよりも微妙です。スタック上のデータとヒープ上のデータは、宣言方法に基づいて絶対的に分かれているわけではありません。例えば:
std::vector<int> v(10);
関数の本体ではvector
、スタック上で10個の整数の(動的配列)を宣言しています。しかし、が管理するストレージvector
はスタック上にありません。
ああ、しかし(他の回答が示唆するように)そのストレージの存続期間はvector
それ自体がスタックベースであるため、それ自体の存続期間によって制限されます。そのため、実装方法に違いはありません-スタックベースのオブジェクトとしてのみ扱うことができます値のセマンティクス。
そうではありません。関数が次のとおりだとします。
void GetSomeNumbers(std::vector<int> &result)
{
std::vector<int> v(10);
// fill v with numbers
result.swap(v);
}
そのため、swap
関数を持つすべてのもの(および複雑な値の型には1つある必要があります)は、そのデータの単一の所有者を保証するシステムの下で、ヒープデータへの一種の再バインド可能な参照として機能できます。
したがって、最新のC ++アプローチは、ヒープデータのアドレスをネイキッドローカルポインター変数に格納しないことです。すべてのヒープ割り当ては、クラス内で非表示にする必要があります。
これを行うと、プログラム内のすべての変数を単純な値型であるかのように考えることができ、ヒープを完全に忘れることができます(一部のヒープデータに新しい値のようなラッパークラスを記述する場合は例外です)。 。
最適化に役立つ特別な知識を1つ保持するだけで済みます。可能な場合は、次のように変数を別の変数に割り当てるのではなく、
a = b;
次のように交換してください:
a.swap(b);
それははるかに速く、例外をスローしないからです。唯一の要件はb
、同じ値を保持し続ける必要がないことです(a
代わりにの値が取得され、これはで破棄されますa = b
)。
欠点は、このアプローチでは、実際の戻り値ではなく、出力パラメーターを介して関数から値を返す必要があることです。しかし、C ++ 0xでは右辺値参照を使用して修正しています。
すべての最も複雑な状況では、このアイデアを一般的な極端にして、shared_ptr
すでにtr1にあるようなスマートポインタクラスを使用します。(必要だと思われる場合は、標準C ++の適用範囲の外に移動した可能性があると私は主張しますが。)
また、アイテムを作成する関数のスコープ外で使用する必要がある場合は、アイテムをヒープに格納します。スタックオブジェクトで使用される1つのイディオムはRAIIと呼ばれます。これには、スタックベースのオブジェクトをリソースのラッパーとして使用することが含まれます。オブジェクトが破棄されると、リソースがクリーンアップされます。スタックベースのオブジェクトは、例外をスローする可能性がある場合に追跡しやすくなります。例外ハンドラでヒープベースのオブジェクトを削除する必要はありません。これが、最新のC ++では生のポインターが通常使用されない理由です。ヒープベースのオブジェクトへの生のポインターのスタックベースのラッパーであるスマートポインターを使用します。
他の答えに加えて、それは少なくとも少しはパフォーマンスについてでもあり得ます。自分に関係のない限り、これについて心配する必要はありませんが、次の点に注意してください。
ヒープに割り当てるには、メモリブロックの追跡を見つける必要があります。これは、一定時間の操作ではありません(また、いくつかのサイクルとオーバーヘッドがかかります)。メモリが断片化したり、アドレススペースの100%を使用したりすると、これは遅くなる可能性があります。一方、スタックの割り当ては一定時間で、基本的には「無料」の操作です。
考慮すべきもう1つの点(繰り返しになりますが、これが問題になる場合にのみ重要です)は、通常、スタックサイズが固定されており、ヒープサイズよりはるかに小さい可能性があることです。したがって、大きなオブジェクトまたは多くの小さなオブジェクトを割り当てる場合は、おそらくヒープを使用する必要があります。スタックスペースが不足すると、ランタイムはサイトタイトル例外をスローします。通常大したことではありませんが、考慮すべきもう1つのことです。
スタックはより効率的で、スコープデータの管理が容易です。
ただし、ヒープは数 KB よりも大きいものに使用する必要があります(C ++では簡単です。boost::scoped_ptr
です。割り当てられたメモリへのポインタを保持するためにスタックにをです)。
それ自体を呼び出し続ける再帰アルゴリズムを考えてみましょう。合計スタック使用量を制限したり推測したりするのは非常に困難です。一方、ヒープ上では、アロケータ(malloc()
またはnew
)は、NULL
またはthrow
ing。
ソース:スタックが8KB以下のLinuxカーネル!
std::unique_ptr
。これは、Boostなどの外部ライブラリよりも優先されるはずです(ただし、時間の経過とともに標準にフィードを提供します)。
おそらくこれはかなりよく答えられています。低レベルの詳細をより深く理解するために、以下の一連の記事を紹介します。Alex Darbyが一連の記事を執筆し、デバッガーを使って説明します。スタックのパート3です。 http://www.altdevblogaday.com/2011/12/14/cc-low-level-curriculum-part-3-the-stack/