C ++のスタック、静的、およびヒープ


160

私は検索しましたが、これらの3つの概念をよく理解していません。動的割り当て(ヒープ内)を使用する必要があるのはいつですか?その実際の利点は何ですか?静的およびスタックの問題は何ですか?ヒープに変数を割り当てずにアプリケーション全体を作成できますか?

他の言語には「ガベージコレクター」が組み込まれているので、メモリを気にする必要はありません。ガベージコレクターは何をしますか?

このガベージコレクタを使用して実行できなかったメモリを自分で操作するにはどうすればよいでしょうか。

誰かがこの宣言でそれを私に言ったら:

int * asafe=new int;

「ポインタへのポインタ」があります。どういう意味ですか?それは次の点で異なります。

asafe=new int;


以前に非常によく似た質問がありました:スタックとヒープは何でどこにありますか?あなたの質問にいくつかの光を当てる必要があるその質問に対するいくつかの本当に良い答えがあります。
スコットサード

回答:


223

同様の質問が行われましたが、静力学については質問されませんでした。

静的、ヒープ、スタックメモリの概要:

  • 静的変数は、グローバルにアクセスできない場合でも、基本的にグローバル変数です。通常、実行可能ファイル自体にそのアドレスがあります。プログラム全体のコピーは1つだけです。関数呼び出し(またはクラス)に何回(およびいくつのスレッドで)実行しても、変数は同じメモリロケーションを参照しています。

  • ヒープは、動的に使用できるメモリの集まりです。オブジェクトに4 KBが必要な場合、動的アロケータはヒープ内の空き領域のリストを調べ、4 KBのチャンクを取り出して、それを提供します。一般に、動的メモリアロケータ(malloc、newなど)はメモリの最後から始まり、逆方向に動作します。

  • スタックがどのように拡大および縮小するかを説明することは、この回答の範囲を少し超えていますが、常に最後からのみ追加および削除するというだけで十分です。スタックは通常、上位から始まり、下位のアドレスにまで成長します。スタックが途中で動的アロケーターに出会うと、メモリが不足します(ただし、物理メモリと仮想メモリ、および断片化を参照)。複数のスレッドには複数のスタックが必要です(プロセスは通常、スタックの最小サイズを予約します)。

それぞれを使用したい場合:

  • 静的/グローバルは、常に必要であり、割り当てを解除したくないことを知っているメモリに役立ちます。(ちなみに、組み込み環境は静的メモリしかないと考えられるかもしれません...スタックとヒープは、3番目のメモリタイプ(プログラムコード)によって共有される既知のアドレス空間の一部です。プログラムは、多くの場合、動的割り当てを行いますリンクされたリストなどが必要な場合の静的メモリ。ただし、静的メモリ自体(バッファ)自体は「割り当て」られているのではなく、この目的のために、バッファが保持しているメモリから他のオブジェクトが割り当てられています。これを行うことができます。非組み込みでも同様に、コンソールゲームは組み込みの動的メモリメカニズムを頻繁に避け、すべての割り当てに事前設定されたサイズのバッファを使用して割り当てプロセスを厳密に制御します。)

  • スタック変数は、関数がスコープ内(スタックのどこか)にある限り、変数を残したい場合に役立ちます。スタックは、それらが配置されているコードに必要な変数に適していますが、そのコード以外では必要ありません。また、ファイルなどのリソースにアクセスしていて、そのコードを離れるとリソースが自動的に削除されるようにする場合にも非常に便利です。

  • ヒープ割り当て(動的に割り当てられたメモリ)は、上記より柔軟にしたい場合に役立ちます。多くの場合、イベントに応答するために関数が呼び出されます(ユーザーが「ボックスの作成」ボタンをクリックします)。適切な応答は、関数が終了した後もずっと留まるはずの新しいオブジェクト(新しいBoxオブジェクト)を割り当てる必要がある場合があるため、スタックに置くことはできません。ただし、プログラムの開始時に必要なボックスの数がわからないため、静的にすることはできません。

ガベージコレクション

最近、ガーベッジ・コレクターの素晴らしさをよく耳にしますので、少し反対意見を述べるとよいでしょう。

ガベージコレクションは、パフォーマンスに大きな問題がない場合に最適なメカニズムです。GCはより良く、より洗練されていると聞いていますが、実際には、(ユースケースによっては)パフォーマンスのペナルティを受け入れざるを得ない場合があります。また、怠惰な場合でも、正常に動作しない可能性があります。ガベージコレクターは、メモリへの参照がなくなったことに気づくと、メモリがなくなることを認識しています(参照カウント参照)。)。ただし、それ自体を参照するオブジェクトがある場合(おそらく、別のオブジェクトを参照することによって)、参照カウントだけでは、メモリを削除できることを示しません。この場合、GCはリファレンススープ全体を調べ、自分だけが参照するアイランドがあるかどうかを調べる必要があります。オフハンドでは、O(n ^ 2)演算になると思いますが、それが何であれ、パフォーマンスにまったく関心がある場合は、悪くなる可能性があります。(編集:Martin B は、適度に効率的なアルゴリズムではO(n)であると指摘しています。パフォーマンスに関心があり、ガベージコレクションなしで一定時間内に割り当てを解除できる場合、それはまだO(n)です。)

個人的に、C ++にはガベージコレクションがないと人々が言うのを聞いたとき、私の心はC ++の機能としてタグ付けしますが、私はおそらく少数派です。おそらく、人々がCおよびC ++でのプログラミングについて学ぶのが最も難しいのは、ポインタと、それらの動的メモリ割り当てを正しく処理する方法です。Pythonのような他のいくつかの言語は、GCがなければ恐ろしいものになるでしょう。そのため、言語から何を求めているかが問題だと思います。信頼できるパフォーマンスが必要な場合、ガベージコレクションなしのC ++は、Fortranのこちら側で考えることができる唯一のものです。使いやすさとトレーニングホイールが必要な場合(「適切な」メモリ管理を習得しなくてもクラッシュするのを防ぐため)、GCで何かを選択します。メモリをうまく管理する方法を知っていても、他のコードの最適化に費やすことができる時間を節約できます。パフォーマンスのペナルティはほとんどなくなりましたが、信頼できるパフォーマンス(および何が起こっているかをいつ、何が起こっているかを正確に知る能力)が本当に必要な場合は、C ++を使用します。私が聞いたことのあるすべての主要なゲームエンジンがC ++(Cまたはアセンブリでない場合)であるという理由があります。Pythonなどはスクリプト作成には適していますが、メインのゲームエンジンには適していません。


元の質問とはあまり関係ありません(実際にはまったく関係ありません)が、スタックとヒープの位置は逆になっています。 通常、スタックは大きくなり、ヒープも大きくなります(実際にはヒープが「大きくなる」わけではないため、これは非常に単純化さ
P Daddy

この質問は他の質問と似ていたり、重複しているとは思わない。これは特にC ++に関するものであり、彼が意味したことはほぼ確実にC ++に存在する3つのストレージ期間です。静的オブジェクトに動的オブジェクトを割り当てることもできます。たとえば、op newをオーバーロードします。
ヨハネスシャウブ-litb 2009年

7
ガベージコレクションに対する軽蔑的な扱いは、役に立たなかった。
Pダディ

9
多くの場合、ガベージコレクションは、パフォーマンスを他の方法で使用できる場合に発生する可能性のあるメモリの解放とは対照的に、実行する作業がほとんどない場合に発生するため、最近は手動でメモリを解放するよりも優れています。
GeorgSchölly2009

3
ほんの少しのコメント-ガベージコレクションにはO(n ^ 2)の複雑さはありません(実際、パフォーマンスに悪影響を及ぼします)。1回のガベージコレクションサイクルにかかる時間は、ヒープのサイズに比例します。hpl.hp.com/ personal / Hans_Boehm / gc / complexity.htmlを参照してください。
マーティンB

54

もちろん、以下はすべて正確ではありません。あなたがそれを読むとき、塩の粒と一緒にそれを取ってください:)

さて、あなたが言及する3つのことは、自動、静的、および動的なストレージ期間です。これは、オブジェクトの存続期間とオブジェクトの存続期間に関係しています。


自動保存期間

短命小さなデータの自動ストレージ期間を使用します。これは、一部のブロック内でローカルにのみ必要です。

if(some condition) {
    int a[3]; // array a has automatic storage duration
    fill_it(a);
    print_it(a);
}

寿命は、ブロックを終了するとすぐに終了し、オブジェクトが定義されるとすぐに始まります。これらは最も単純な種類のストレージ期間であり、特定の動的ストレージ期間よりもはるかに高速です。


静的ストレージ期間

スコープがそのような使用を許可する場合(名前空間スコープ)、スコープがその出口を越えて存続期間を延長する必要があるローカル変数(ローカルスコープ)には、任意のコードによって常にアクセスされる可能性がある自由変数に静的ストレージ期間を使用します。クラス(クラススコープ)のすべてのオブジェクトで共有する必要があるメンバー変数用。それらの存続期間は、そのスコープに依存します。名前空間スコープローカルスコープクラススコープを持つことができます。どちらの場合も、彼らの人生が始まると、プログラムの終わりに人生が終わります。次に2つの例を示します。

// static storage duration. in global namespace scope
string globalA; 
int main() {
    foo();
    foo();
}

void foo() {
    // static storage duration. in local scope
    static string localA;
    localA += "ab"
    cout << localA;
}

ブロックの終了時に破棄されないabababため、プログラムはを出力localAします。ローカルスコープを持つオブジェクトは、制御がその定義に到達したときに存続期間を開始すると言えます。のlocalA場合、関数の本体に入ったときに発生します。名前空間スコープ内のオブジェクトの場合、存続期間はプログラムの起動時に始まります。クラススコープの静的オブジェクトについても同様です。

class A {
    static string classScopeA;
};

string A::classScopeA;

A a, b; &a.classScopeA == &b.classScopeA == &A::classScopeA;

ご覧のとおり、classScopeAはそのクラスの特定のオブジェクトにバインドされていませんが、クラス自体にバインドされています。上記の3つの名前すべてのアドレスは同じで、すべて同じオブジェクトを示します。静的オブジェクトがいつどのように初期化されるかについては特別なルールがありますが、ここではそれについては気にしません。これは、静的初期化順序fiascoという用語が意味します。


動的ストレージ期間

最後の保存期間は動的です。オブジェクトを別の島に配置し、それらを参照するポインターを配置したい場合に使用します。オブジェクトが大きい場合や、実行時にのみ既知のサイズの配列を作成する場合にも使用します。この柔軟性のため、動的な保存期間を持つオブジェクトは複雑で、管理が遅くなります。その動的な期間を持つオブジェクトは、適切な新しいオペレーター呼び出しが発生したときに存続期間を開始します。

int main() {
    // the object that s points to has dynamic storage 
    // duration
    string *s = new string;
    // pass a pointer pointing to the object around. 
    // the object itself isn't touched
    foo(s);
    delete s;
}

void foo(string *s) {
    cout << s->size();
}

それらの寿命は、削除を呼び出すときにのみ終了します。それを忘れた場合、それらのオブジェクトは寿命を終えることはありません。また、ユーザーが宣言したコンストラクタを定義するクラスオブジェクトは、デストラクタが呼び出されません。動的なストレージ期間を持つオブジェクトは、そのライフタイムと関連するメモリリソースを手動で処理する必要があります。ライブラリは、それらの使用を容易にするために存在します。特定のオブジェクトの明示的なガベージコレクションは、スマートポインターを使用して確立できます。

int main() {
    shared_ptr<string> s(new string);
    foo(s);
}

void foo(shared_ptr<string> s) {
    cout << s->size();
}

deleteの呼び出しについて気にする必要はありません。オブジェクトを参照する最後のポインターがスコープから外れた場合、共有ptrが代わりに実行します。共有ptr自体には自動保存期間があります。したがって、その存続期間は自動的に管理され、デストラクタ内のポイントされた動的オブジェクトを削除する必要があるかどうかを確認できます。shared_ptrのリファレンスについては、ブーストドキュメントを参照してください:http : //www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm


39

「短い答え」のように、それは入念に言われました:

  • 静的変数(クラス)
    ライフタイム=プログラムランタイム(1)
    可視性=アクセス修飾子によって決定(プライベート/保護/パブリック)

  • 静的変数(グローバルスコープ)
    ライフタイム=プログラムランタイム(1)
    可視性=(2)でインスタンス化されるコンパイル単位

  • ヒープ変数の
    存続期間= ユーザーが定義(新規削除)
    可視性=ユーザーが定義(ポインターを割り当てたものはすべて)

  • スタック変数の
    可視性=宣言からスコープが終了するまでの
    存続期間=宣言からスコープの宣言が終了するまで


(1)より正確には、コンパイル単位の初期化から非初期化まで(つまり、C / C ++ファイル)。コンパイル単位の初期化の順序は、標準では定義されていません。

(2)注意:静的変数をヘッダーでインスタンス化すると、各コンパイル単位は独自のコピーを取得します。


5

きっとすぐに良い答えが出てくると思いますが、主な違いはスピードとサイズです。

スタック

割り当てが劇的に速くなります。スタックフレームの設定時に割り当てられるため、O(1)で行われ、本質的に解放されます。欠点は、スタックスペースが不足すると、骨が折れることです。スタックサイズは調整できますが、IIRCには2 MBまで使用できます。また、関数を終了するとすぐに、スタック上のすべてがクリアされます。したがって、後で参照することは問題になる可能性があります。(割り当てられたオブジェクトをスタックするポインターはバグの原因になります。)

ヒープ

割り当てが劇的に遅くなります。しかし、GBをいじって、ポイントする必要があります。

ガベージコレクター

ガベージコレクターは、バックグラウンドで実行され、メモリを解放するコードです。ヒープにメモリを割り当てると、メモリリークと呼ばれる解放を忘れがちです。時間の経過とともに、アプリケーションが消費するメモリは、クラッシュするまで増加し続けます。不要なメモリを定期的にガベージコレクターで解放すると、このクラスのバグを排除できます。もちろん、ガベージコレクタが物事を遅くするため、これには代償が伴います。


3

静的およびスタックの問題は何ですか?

「静的」割り当ての問題は、割り当てがコンパイル時に行われることです。これを使用して、実行時までわからないデータの可変数を割り当てることはできません。

「スタック」での割り当ての問題は、割り当てを行うサブルーチンが戻るとすぐに割り当てが破棄されることです。

ヒープに変数を割り当てずにアプリケーション全体を作成できますか?

おそらく、重要ではない通常の大きなアプリケーションではありません(ただし、いわゆる「組み込み」プログラムは、C ++のサブセットを使用して、ヒープなしで作成される場合があります)。

ガベージコレクターは何をしますか?

データを監視し続け(「マークアンドスイープ」)、アプリケーションがそれを参照しなくなったときを検出します。アプリケーションはデータの割り当てを解除する必要がないため、これはアプリケーションにとって便利ですが、ガベージコレクターは計算コストがかかる可能性があります。

ガベージコレクターは、C ++プログラミングの通常の機能ではありません。

このガベージコレクタを使用して実行できなかったメモリを自分で操作するにはどうすればよいでしょうか。

確定的なメモリ割り当て解除のためのC ++メカニズムについて学習します。

  • 「静的」:割り当て解除されない
  • 'スタック':変数が「範囲外」になり次第
  • 「ヒープ」:ポインターが削除されたとき(アプリケーションによって明示的に削除された、または何らかのサブルーチン内で暗黙的に削除された)

1

スタックのメモリ割り当て(関数変数、ローカル変数)は、スタックが "深すぎる"場合に問題となり、スタックの割り当てに使用できるメモリがオーバーフローします。ヒープは、複数のスレッドから、またはプログラムのライフサイクルを通じてアクセスする必要があるオブジェクト用です。ヒープを使わずにプログラム全体を書くことができます。

ガベージコレクタがなくてもメモリは簡単にリークできますが、オブジェクトとメモリを解放するタイミングを指定することもできます。GCを実行するとJavaの問題が発生し、GCは排他的なスレッド(他に実行できるスレッドはない)なので、リアルタイムプロセスがあります。したがって、パフォーマンスが重要であり、オブジェクトのリークがないことを保証できる場合は、GCを使用しないことが非常に役立ちます。それ以外の場合は、アプリケーションがメモリを消費し、リークの原因を突き止める必要があるときに、命を憎むだけです。


1

プログラムが割り当てるメモリ量を事前に把握していない場合(スタック変数を使用できないため)。リンクされたリストについて言えば、リストのサイズが事前にわからなくてもリストは大きくなる可能性があります。したがって、リンクリストに挿入される要素の数がわからない場合は、リンクリストに対してヒープに割り当てることは理にかなっています。


0

一部の状況でのGCの利点は、他の状況では不快です。GCへの依存は、それについてあまり考えないことを奨励します。理論的には、「アイドル」期間または絶対に必要な期間まで待機します。帯域幅を盗み、アプリで応答レイテンシを引き起こします。

しかし、「考える必要はない」必要はありません。マルチスレッドアプリの他のすべてと同じように、譲れるときは譲ることができます。たとえば、.Netでは、GCを要求することができます。これを行うことで、GCの実行頻度が低くなる代わりに、GCの実行頻度が低くなり、このオーバーヘッドに関連するレイテンシを分散させることができます。

しかし、これは「自動マットであるため、あまり考える必要がないように奨励されている」と思われるGCの主要な魅力を打ち負かしています。

GCが普及する前にプログラミングに初めて触れ、malloc / freeとnew / deleteに慣れている場合は、GCが少し面倒である、または不信感を感じることもあります(「チェッカーの履歴がありました。)多くのアプリはランダムなレイテンシを許容しています。しかし、ランダムレイテンシがあまり受け入れられない、そうでないアプリの場合、一般的な反応は、GC環境を避け、純粋にアンマネージコード(または禁じられている神、長い間死ぬ芸術、アセンブリ言語)の方向に移動することです。

私はしばらく前にここに夏の学生、GCで離乳したインターンの賢い子供がいました。管理されていないC / C ++でプログラミングしている場合でも、malloc / free new / deleteモデルに従うことを拒否したため、「現代のプログラミング言語でこれを行う必要はない」と引用しているためです。あなたが知っています?実行時間の短いアプリの場合は、実際にそれを回避できますが、実行時間の長いアプリの場合はできません。


0

スタックはコンパイラによって割り当てられるメモリであり、プログラムをコンパイルするときは常に、デフォルトのコンパイラはOSからメモリを割り当てます(IDEのコンパイラ設定から設定を変更できます)。OSはメモリを提供するメモリであり、OSによって異なりますシステムで利用可能な多くのメモリやその他多くのもので、それらがコピーする変数を宣言すると、メモリがスタックに割り当てられます(形式として参照)。これらの変数はスタックにプッシュされ、デフォルトでVisual StudioのCDECLのいくつかの命名規則に従います例:中置記法:c = a + b; スタックのプッシュは、右から左へのプッシュ、スタックへのb、演算子、スタックへのa、それらのi、ecのスタックの結果です。プレフィックス表記:= + cabここでは、すべての変数が1番目(右から左)にスタックにプッシュされ、操作が行われます。コンパイラによって割り当てられるこのメモリは修正されています。したがって、1MBのメモリがアプリケーションに割り当てられているとします。変数は700kbのメモリを使用します(動的に割り当てられない限り、すべてのローカル変数はスタックにプッシュされます)。残りの324kbのメモリがヒープに割り当てられます。そして、このスタックの寿命は短く、関数のスコープが終了すると、これらのスタックはクリアされます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.