私はしばらくプログラミングをしていますが、ほとんどが Java と C# です。実際に自分でメモリを管理する必要はありませんでした。私は最近 C++ でプログラミングを始めましたが、スタックに格納するタイミングとヒープに格納するタイミングについて少し混乱しています。
私の理解では、非常に頻繁にアクセスされる変数はスタックとオブジェクトに格納する必要があり、めったに使用されない変数、および大きなデータ構造はすべてヒープに格納する必要があります。これは正しいですか、それとも間違っていますか?
私はしばらくプログラミングをしていますが、ほとんどが Java と C# です。実際に自分でメモリを管理する必要はありませんでした。私は最近 C++ でプログラミングを始めましたが、スタックに格納するタイミングとヒープに格納するタイミングについて少し混乱しています。
私の理解では、非常に頻繁にアクセスされる変数はスタックとオブジェクトに格納する必要があり、めったに使用されない変数、および大きなデータ構造はすべてヒープに格納する必要があります。これは正しいですか、それとも間違っていますか?
いいえ、スタックとヒープの違いはパフォーマンスではありません。それは寿命です: 関数内のローカル変数 (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
関数を持つもの (および複雑な値の型は関数を持つ必要があります) は、そのデータの単一の所有者を保証するシステムの下で、ヒープ データへの一種の再バインド可能な参照として機能できます。
したがって、最新の 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()
または) は returnまたはingnew
によってメモリ不足を示すことができます。NULL
throw
出典: スタックが 8KB 以下の Linux カーネル!
完全を期すために、組み込みソフトウェアのコンテキストでヒープを使用する際の問題に関するMiroSamekの記事を読むことができます。
ヒープに割り当てるかスタックに割り当てるかの選択は、変数の割り当て方法に応じて、あなたのために行われます。「新しい」呼び出しを使用して何かを動的に割り当てる場合、ヒープから割り当てています。何かをグローバル変数として、または関数のパラメーターとして割り当てると、スタックに割り当てられます。
私の意見では、決定的な要因は2つあります
1) Scope of variable
2) Performance.
ほとんどの場合、スタックを使用したいと思いますが、スコープ外の変数にアクセスする必要がある場合は、ヒープを使用できます。
ヒープを使用しながらパフォーマンスを向上させるために、機能を使用してヒープ ブロックを作成することもできます。これは、各変数を異なるメモリ位置に割り当てるのではなく、パフォーマンスを向上させるのに役立ちます。
おそらくこれはかなりうまく答えられています。低レベルの詳細をより深く理解するために、以下の一連の記事を参照してください。Alex Darby は一連の記事を書いており、デバッガーを使って説明しています。スタックについてのパート 3 です。 http://www.altdevblogaday.com/2011/12/14/cc-low-level-curriculum-part-3-the-stack/