C ++でのスタックとヒープの適切な使用法


122

私はしばらくプログラミングをしてきましたが、ほとんどがJavaとC#でした。実際に自分でメモリを管理する必要はありませんでした。私は最近C ++でプログラミングを始めましたが、いつスタックに格納するか、いつヒープに格納するかについて少し混乱しています。

非常に頻繁にアクセスされる変数はほとんど使用されない変数であるスタックとオブジェクトに格納する必要があり、大きなデータ構造はすべてヒープに格納する必要があると私は理解しています。これは正しいですか、それとも間違っていますか?


回答:


242

いいえ、スタックとヒープの違いはパフォーマンスではありません。それは寿命です。関数内のローカル変数(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;
}

スタックが何であるかをより明確に理解するには、高レベル言語の観点からスタックの機能を理解しようとするのではなく、「コールスタック」と「呼び出し規約」を調べて何を参照するかを検討してください。関数を呼び出すと、マシンは実際に実行します。コンピュータのメモリは単なる一連のアドレスです。「ヒープ」と「スタック」はコンパイラの発明です。


7
可変サイズの情報は一般的にヒープに追加されることを追加しても安全です。私が知っている唯一の例外は、C99のVLA(サポートが制限されている)と、Cプログラマーによってさえ誤解されがちなalloca()関数です。
ダン・オルソン

10
良い説明ですが、割り当てや割り当て解除が頻繁に行われるマルチスレッドのシナリオで、ヒープ競合のポイントになるため、パフォーマンスに影響します。それでも、スコープはほとんど常に決定的な要素です。
peterchen 2009年

18
もちろん、new / malloc()自体は処理が遅く、スタックは任意のヒープ行よりもdcacheにある可能性が高くなります。これらは実際の考慮事項ですが、通常は寿命の問題の二次的なものです。
Crashworks、

1
「コンピュータのメモリは単なる一連のアドレスであり、「ヒープ」と「スタック」はコンパイルの発明である」というのは本当ですか?多くの場所で、スタックがコンピューターのメモリの特別な領域であるということを読んだことがあります。
Vineeth Chitteti 2014

2
@kaiそれはそれを視覚化する方法ですが、必ずしも物理的に話すとは限りません。OSは、アプリケーションのスタックとヒープの割り当てを担当します。コンパイラも責任がありますが、主にOSに依存しています。スタックには制限があり、ヒープには制限がありません。これは、複数のアプリケーションが同じシステムで実行できるように、OSがこれらのメモリアドレスをより構造化されたものに分類する方法によるものです。ヒープとスタックだけではありませんが、通常、ほとんどの開発者が懸念しているのはヒープとスタックだけです。
tsturzl 2015

42

私は言うでしょう:

できればスタックに格納します。

必要な場合は、ヒープに保管してください。

したがって、ヒープよりもスタックを優先します。スタックに何かを格納できないいくつかの考えられる理由は次のとおりです。

  • それは大きすぎます-32ビットOSのマルチスレッドプログラムでは、スタックのサイズは(少なくともスレッド作成時に)固定されており(通常は数メガバイトです。これにより、アドレスを使い果たすことなく多くのスレッドを作成できます。スペース。64ビットプログラムまたはシングルスレッド(Linuxとにかく)プログラムの場合、これは大きな問題ではありません。32ビットLinuxでは、シングルスレッドプログラムは通常、ダイナミックスタックを使用し、ヒープの先頭に到達するまで成長し続けます。
  • 元のスタックフレームのスコープ外でアクセスする必要があります-これが本当に主な理由です。

賢明なコンパイラを使用すると、ヒープに固定されていないサイズのオブジェクト(通常、コンパイル時にサイズがわからない配列)を割り当てることができます。


1
通常、2 KBを超えるものはヒープに配置するのが最適です。詳細はわかりませんが、「数メガ」のスタックで作業したことを覚えていません。
ダン・オルソン

2
それは私が最初にユーザーに関係しないものです。ユーザーにとって、STLがコンテンツをヒープに格納していても、ベクトルとリストはスタックに割り当てられているように見えます。いつnew / deleteを明示的に呼び出すかを決定する行についての質問がより多く思われた。
デビッドロドリゲス-ドリベス2009年

1
ダン:私は32ギガバイトのLinuxでスタックに2ギグ(はい、ギグのようにG)を入れました。スタック制限はOSに依存します。
Mr.Ree

6
mrree:Nintendo DSスタックは16キロバイトです。一部のスタック制限はハードウェアに依存します。
Antの2010年

Ant:すべてのスタックは、ハードウェア依存、OS依存、およびコンパイラ依存です。
Viliami 2016

24

他の回答が示唆するよりも微妙です。スタック上のデータとヒープ上のデータは、宣言方法に基づいて絶対的に分かれているわけではありません。例えば:

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 ++の適用範囲の外に移動した可能性があると私は主張しますが。)


6

また、アイテムを作成する関数のスコープ外で使用する必要がある場合は、アイテムをヒープに格納します。スタックオブジェクトで使用される1つのイディオムはRAIIと呼ばれます。これには、スタックベースのオブジェクトをリソースのラッパーとして使用することが含まれます。オブジェクトが破棄されると、リソースがクリーンアップされます。スタックベースのオブジェクトは、例外をスローする可能性がある場合に追跡しやすくなります。例外ハンドラでヒープベースのオブジェクトを削除する必要はありません。これが、最新のC ++では生のポインターが通常使用されない理由です。ヒープベースのオブジェクトへの生のポインターのスタックベースのラッパーであるスマートポインターを使用します。


5

他の答えに加えて、それは少なくとも少しはパフォーマンスについてでもあり得ます。自分に関係のない限り、これについて心配する必要はありませんが、次の点に注意してください。

ヒープに割り当てるには、メモリブロックの追跡を見つける必要があります。これは、一定時間の操作ではありません(また、いくつかのサイクルとオーバーヘッドがかかります)。メモリが断片化したり、アドレススペースの100%を使用したりすると、これは遅くなる可能性があります。一方、スタックの割り当ては一定時間で、基本的には「無料」の操作です。

考慮すべきもう1つの点(繰り返しになりますが、これが問題になる場合にのみ重要です)は、通常、スタックサイズが固定されており、ヒープサイズよりはるかに小さい可能性があることです。したがって、大きなオブジェクトまたは多くの小さなオブジェクトを割り当てる場合は、おそらくヒープを使用する必要があります。スタックスペースが不足すると、ランタイムはサイトタイトル例外をスローします。通常大したことではありませんが、考慮すべきもう1つのことです。


ヒープとスタックの両方がページングされた仮想メモリです。ヒープ検索時間は、新しいメモリにマップするために必要な時間に比べて非常に高速です。32ビットLinuxでは、> 2gigをスタックに配置できます。Macでは、スタックは65Megにハード制限されていると思います。
Mr.Ree

3

スタックはより効率的で、スコープデータの管理が容易です。

ただし、ヒープは KB よりも大きいものに使用する必要があります(C ++では簡単です。boost::scoped_ptrです。割り当てられたメモリへのポインタを保持するためにスタックにをです)。

それ自体を呼び出し続ける再帰アルゴリズムを考えてみましょう。合計スタック使用量を制限したり推測したりするのは非常に困難です。一方、ヒープ上では、アロケータ(malloc()またはnew)は、NULLまたはthrow ing。

ソース:スタックが8KB以下のLinuxカーネル!


他の読者の参考のために:(A)ここでの「すべき」は純粋にユーザーの個人的な見解であり、多くても多くのユーザーが遭遇する可能性が低い(再帰)1つの引用と1つのシナリオに基づいています。また、(B)標準ライブラリはを提供しますstd::unique_ptr。これは、Boostなどの外部ライブラリよりも優先されるはずです(ただし、時間の経過とともに標準にフィードを提供します)。
underscore_d


1

ヒープに割り当てるかスタックに割り当てるかは、変数の割り当て方法に応じて自動的に選択されます。「新しい」呼び出しを使用して動的に何かを割り当てる場合、ヒープから割り当てます。グローバル変数として、または関数のパラメーターとして何かを割り当てる場合、スタックに割り当てられます。


4
私は彼が物をヒープにいつ置くかではなく、どのように置くかを尋ねていたのではないかと思います。
スティーブ・ロウ

0

私の意見では、2つの決定要因があります

1) Scope of variable
2) Performance.

私はほとんどの場合スタックを使用したいと思いますが、スコープ外の変数にアクセスする必要がある場合は、ヒープを使用できます。

ヒープを使用しながらパフォーマンスを向上させるには、機能を使用してヒープブロックを作成することもできます。これにより、各変数を異なるメモリ位置に割り当てるのではなく、パフォーマンスを向上させることができます。


0

おそらくこれはかなりよく答えられています。低レベルの詳細をより深く理解するために、以下の一連の記事を紹介します。Alex Darbyが一連の記事を執筆し、デバッガーを使って説明します。スタックのパート3です。 http://www.altdevblogaday.com/2011/12/14/cc-low-level-curriculum-part-3-the-stack/


リンクは無効になっているように見えますが、インターネットアーカイブウェイバックマシンをチェックすると、スタックについてのみ話しているため、ここでのスタックヒープに関する特定の質問に答えることは何もありません。-1
underscore_d
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.