web-dev-qa-db-ja.com

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

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

他の言語には「ガベージコレクター」が組み込まれているので、記憶について心配する必要はないと聞きました。ガベージコレクターは何をしますか?

このガベージコレクターを使用して実行できなかったメモリを自分で操作できますか?

この宣言で誰かが私に言ったとき:

int * asafe=new int;

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

asafe=new int;

153
Hai

同様の質問 が尋ねられましたが、静的については尋ねませんでした。

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

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

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

  • スタックがどのように拡大および縮小するかを説明することは、この答えの範囲外ですが、常に最後からのみ追加および削除するだけで十分です。スタックは通常、高い位置から始まり、低いアドレスに向かって成長します。スタックが中間のどこかでダイナミックアロケーターに出会うと、メモリが不足します(ただし、物理メモリと仮想メモリおよびフラグメンテーションを参照します)。複数のスレッドには複数のスタックが必要です(通常、プロセスはスタックの最小サイズを予約します)。

それぞれを使用する場合:

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

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

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

Garbage Collection

ガベージコレクターの素晴らしさについて最近よく耳にしましたので、少し異議を唱える声が役立つかもしれません。

ガベージコレクションは、パフォーマンスが大きな問題ではない場合の素晴らしいメカニズムです。 GCはより良く、より洗練されていると聞いていますが、実際には、パフォーマンスのペナルティを受け入れることを余儀なくされる場合があります(ユースケースによって異なります)。そして、あなたが怠けているなら、それはまだ適切に動作しないかもしれません。ガベージコレクターは、メモリへの参照がなくなったことを認識すると、メモリがなくなることをよく考えます( 参照カウント を参照)。ただし、自分自身を参照するオブジェクトがある場合(逆方向に参照する別のオブジェクトを参照する場合など)、参照カウントだけではメモリを削除できることを示しません。この場合、GCは参照スープ全体を調べて、自分だけで参照される島があるかどうかを把握する必要があります。率直に言って、O(n ^ 2)操作であることは推測しますが、それが何であれ、パフォーマンスにまったく関心がある場合は悪くなる可能性があります。 (編集:マーティンB 指摘 合理的に効率的なアルゴリズムではO(n)である。それでもO(n)は多すぎるパフォーマンスに関心があり、ガベージコレクションなしで一定の時間で割り当てを解除できます。)

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

211
markets

もちろん、以下はすべて完全に正確ではありません。あなたがそれを読んだときに塩の粒でそれを取る:)

参照する3つのことは、自動、静的、および動的ストレージ期間です。これは、オブジェクトの存続期間と開始時期に関係しています。


自動保管期間

短命およびsmallデータに自動ストレージ期間を使用します。これは、一部のブロック内でlocallyのみに必要です。

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

ライフタイムは、ブロックを終了するとすぐに終了し、オブジェクトが定義されるとすぐに開始します。それらは最も単純な種類の保存期間であり、特定の動的保存期間よりもはるかに高速です。


静的保存期間

スコープがそのような使用を許可する場合(名前空間スコープ)、およびスコープの出口全体で有効期間を延長する必要があるローカル変数(ローカルスコープ)に、すべてのコードが常にアクセスする可能性のある自由変数に静的ストレージ期間を使用します。クラスのすべてのオブジェクト(クラススコープ)で共有する必要があるメンバー変数の場合。それらの有効期間は、そのスコープに依存します。namespace scopeおよびlocal scopeおよびclass scopeを持つことができます。両方について本当のことは、彼らの人生が始まると、寿命はプログラムの終わりで終わるということです。以下に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の場合、関数の本体が入力されたときに発生します。名前空間スコープのオブジェクトの場合、有効期間はprogram startupで始まります。クラススコープの静的オブジェクトについても同じことが言えます。

class A {
    static string classScopeA;
};

string A::classScopeA;

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

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


動的ストレージ期間

最後の保存期間は動的です。オブジェクトを別の島に配置し、その参照を囲むようにポインターを配置する場合に使用します。オブジェクトがbigの場合、およびruntimeでのみ認識されるサイズの配列を作成する場合にも使用します。この柔軟性のため、動的ストレージ期間を持つオブジェクトは複雑で、管理が遅くなります。その動的期間を持つオブジェクトは、適切なnewオペレーター呼び出しが発生するとライフタイムを開始します。

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();
}

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

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

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

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

「簡潔な答え」のように、精巧に言われています。

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

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

  • ヒープ変数
    lifetime =あなたによって定義されます(削除する新しい)
    可視性=ユーザーによって定義されます(ポインターを割り当てるものは何でも)

  • スタック変数
    visibility =宣言からスコープが終了するまで
    lifetime =宣言から宣言スコープが終了するまで


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

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

37
peterchen

すぐにでも良い答えが出てくると思いますが、主な違いは速度とサイズです。

スタック

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

ヒープ

割り当てが劇的に遅くなります。しかし、あなたはGBを使ってプレイし、ポイントします。

ガベージコレクター

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

5
Chris Smith

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

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

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

ヒープに変数を割り当てることなく、アプリケーション全体を作成できますか?

おそらく、しかし重要なアプリケーションではありませんが、C++のサブセットを使用して、ヒープなしでいわゆる「埋め込み」プログラムを作成できます。

ガベージコレクターの機能

データを監視し続け(「マークアンドスイープ」)、アプリケーションがデータを参照しなくなったことを検出します。アプリケーションはデータの割り当てを解除する必要がないため、これはアプリケーションにとって便利です...

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

このガベージコレクターを使用して実行できなかったメモリを自分で操作できますか?

決定論的なメモリ割り当て解除のC++メカニズムを学習します。

  • 「静的」:割り当て解除されない
  • 'stack':変数が「スコープ外になる」とすぐに
  • 'heap':ポインターが削除されたとき(アプリケーションによって明示的に削除されたとき、または他のサブルーチン内で暗黙的に削除されたとき)
3
ChrisW

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

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

1
Rob Elsner

ある状況でのGCの利点は、他の状況での迷惑です。 GCへの依存は、GCについてあまり考えないことを奨励します。理論的には、「アイドル」期間または絶対に必要になるまで待機し、帯域幅を奪い、アプリで応答遅延を引き起こします。

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

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

GCが流行する前にプログラミングに最初にさらされ、malloc/freeおよびnew/deleteに慣れていた場合、GCが少し迷惑であるか、不信である(あるいは、最適化」という歴史があります。多くのアプリはランダムなレイテンシを許容しています。しかし、ランダムレイテンシが許容されない場合、一般的な反応はGC環境を回避し、純粋にアンマネージコード(または神の禁断、長く死にかけているアート、アセンブリ言語)の方向に進むことです。

私はここにしばらく前に夏休みの生徒がいました。インターンの賢い子供で、GCで離乳しました。彼はGCの優位性に非常に不満だったため、アンマネージC/C++でプログラミングする場合でも、「現代のプログラミング言語でこれを行う必要はない」という理由で、malloc/free new/deleteモデルに従うことを拒否しました。あなたが知っています?小さくて実行時間の短いアプリの場合は、実際にそれを回避できますが、実行時間の長いパフォーマンスアプリの場合はできません。

1
frediano

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

1
kal

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

0
raj