C ++のスタック、静的、およびヒープ
-
03-07-2019 - |
質問
検索しましたが、これら3つの概念をよく理解していません。動的割り当て(ヒープ内)を使用しなければならないのはいつですか?その本当の利点は何ですか?静的およびスタックの問題は何ですか?ヒープに変数を割り当てずにアプリケーション全体を作成できますか?
他の言語には「ガベージコレクター」が組み込まれていると聞きました。そのため、メモリについて心配する必要はありません。ガベージコレクターは何をしますか?
このガベージコレクターを使用してできなかったメモリを自分で操作することができますか?
次の宣言で誰かが私に言った:
int * asafe=new int;
「ポインタへのポインタ」があります。どういう意味ですか?異なるのは:
asafe=new int;
?
解決
同様の質問が尋ねられましたが、そうではありませんでした静的については尋ねません。
静的メモリ、ヒープメモリ、スタックメモリの概要:
-
静的変数は、グローバルにアクセスできない場合でも、基本的にはグローバル変数です。通常、実行可能ファイル自体にあるアドレスがあります。プログラム全体のコピーは1つだけです。関数呼び出し(またはクラス)を何回(およびスレッド数で)行っても、変数は同じメモリ位置を参照しています。
-
ヒープは、動的に使用できるメモリの束です。オブジェクトに4kbが必要な場合、ダイナミックアロケータはヒープ内の空き領域のリストを調べ、4kbのチャンクを選択して、それを提供します。一般に、動的メモリアロケータ(malloc、newなど)はメモリの最後から始まり、逆方向に動作します。
-
スタックがどのように拡大および縮小するかを説明することは、この答えの範囲外です。ただし、常に最後から追加および削除するだけで十分です。スタックは通常、高位から始まり、アドレスが低くなるにつれて大きくなります。スタックが中間のどこかでダイナミックアロケーターに出会うと、メモリが不足します(ただし、物理メモリと仮想メモリおよびフラグメンテーションを参照します)。複数のスレッドには複数のスタックが必要です(通常、プロセスはスタックの最小サイズを予約します)。
それぞれを使用する場合:
-
Statics / globalsは、常に必要であり、割り当てを解除したくないことがわかっているメモリに役立ちます。 (ところで、組み込み環境は静的メモリのみを持つと考えられるかもしれません...スタックとヒープは、3番目のメモリタイプであるプログラムコードによって共有される既知のアドレス空間の一部です。プログラムは、多くの場合、リンクリストのようなものが必要な場合は静的メモリですが、それにもかかわらず、静的メモリ自体(バッファ)自体は「割り当て」ではなく、この目的のためにバッファが保持するメモリから他のオブジェクトが割り当てられます。これは非埋め込みでも同様であり、コンソールゲームは組み込みの動的メモリメカニズムを頻繁に避け、すべての割り当てにプリセットサイズのバッファを使用して割り当てプロセスを厳密に制御します。)
-
スタック変数は、関数が(スタック上のどこかに)スコープ内にある限り、変数を残したいことがわかっている場合に役立ちます。スタックは、それらが配置されているコードに必要な変数には適していますが、そのコード以外では必要ありません。また、ファイルなどのリソースにアクセスし、そのコードを離れるとリソースが自動的になくなるようにする場合にも便利です。
-
ヒープ割り当て(動的に割り当てられたメモリ)は、上記よりも柔軟にしたい場合に便利です。多くの場合、イベントに応答するために関数が呼び出されます(ユーザーが[ボックスの作成]ボタンをクリックします)。適切な応答には、関数が終了した後ずっとスタックする必要がある新しいオブジェクト(新しいBoxオブジェクト)を割り当てる必要があるため、スタックに配置できません。ただし、プログラムの開始時に必要なボックスの数がわからないため、静的にすることはできません。
ガベージコレクション
最近、ガベージコレクターの素晴らしさについて多くのことを聞いたので、少し異議を唱える声が役立つかもしれません。
Garbage Collectionは、パフォーマンスが大きな問題ではない場合の素晴らしいメカニズムです。 GCはより良く、より洗練されていると聞いていますが、実際には、パフォーマンスのペナルティを受け入れることを余儀なくされる場合があります(ユースケースによって異なります)。そして、あなたが怠けているなら、それはまだ適切に動作しないかもしれません。ガベージコレクターは、メモリへの参照がこれ以上ないことを認識すると、メモリがなくなることをよく考えています(
他のヒント
もちろん、以下はすべて正確ではありません。読むときは一粒の塩でそれを取りなさい:)
まあ、あなたが言及する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;
}
localA
はブロックの終了時に破棄されないため、プログラムは ababab
を出力します。ローカルスコープを持つオブジェクトは、制御が定義に達すると有効期間を開始すると言うことができます。 localA
の場合、関数の本体が入力されたときに発生します。名前空間スコープのオブジェクトの場合、有効期間はプログラムの起動から始まります。同じことがクラススコープの静的オブジェクトにも当てはまります:
class A {
static string classScopeA;
};
string A::classScopeA;
A a, b; &a.classScopeA == &b.classScopeA == &A::classScopeA;
ご覧のとおり、 classScopeA
はそのクラスの特定のオブジェクトではなく、クラス自体にバインドされています。上記の3つの名前すべてのアドレスは同じであり、すべて同じオブジェクトを示しています。静的オブジェクトをいつ、どのように初期化するかについての特別なルールがありますが、今は気にしません。これは、静的初期化順序の大失敗という用語が意味します。
動的ストレージ期間
最後の保存期間は動的です。オブジェクトを別の島に配置し、その参照を囲むようにポインターを配置する場合に使用します。オブジェクトが大きいの場合、および実行時でのみ知られているサイズの配列を作成する場合にも使用します。この柔軟性のため、動的ストレージ期間を持つオブジェクトは複雑で、管理が遅くなります。適切な 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 を呼び出した場合にのみ終了します。それを忘れると、それらのオブジェクトは寿命を終えることはありません。また、ユーザーが宣言したコンストラクタを定義するクラスオブジェクトは、デストラクタが呼び出されません。動的ストレージ期間を持つオブジェクトは、そのライフタイムと関連するメモリリソースの手動処理を必要とします。ライブラリは、それらの使用を容易にするために存在します。 特定のオブジェクトの明示的なガベージコレクションは、スマートポインターを使用して確立できます。
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_p
「短い答え」のように、精巧に言われています:
-
静的変数(クラス)
ライフタイム=プログラムランタイム(1)
可視性=アクセス修飾子(プライベート/保護/パブリック)によって決定 -
静的変数(グローバルスコープ)
ライフタイム=プログラムランタイム(1)
可視性=(2)でインスタンス化されるコンパイル単位 -
ヒープ変数
ライフタイム=自分で定義(削除するのは初めて)
可視性=ユーザーによって定義されます(ポインターを割り当てるものは何でも) -
スタック変数
可視性=宣言からスコープが終了するまで
lifetime =宣言から宣言スコープが終了するまで
(1)より正確に:初期化からコンパイル単位(つまりC / C ++ファイル)の初期化解除まで。コンパイル単位の初期化の順序は、標準では定義されていません。
(2)注意:ヘッダーで静的変数をインスタンス化する場合、各コンパイルユニットは独自のコピーを取得します。
まもなく、ペダルの1つがより良い答えを出すと思いますが、主な違いは速度とサイズです。
スタック
割り当てが劇的に高速になりました。スタックフレームのセットアップ時に割り当てられるため、O(1)で行われるため、本質的にはフリーです。欠点は、スタックスペースが不足すると骨が折れることです。スタックサイズは調整できますが、IIRCでは2MBまで使用できます。また、関数を終了するとすぐに、スタック上のすべてがクリアされます。そのため、後で参照するのは問題になる可能性があります。 (スタックに割り当てられたオブジェクトへのポインターはバグにつながります。)
ヒープ
割り当てが劇的に遅くなります。しかし、あなたはGBを使ってプレイし、ポイントします。
ガベージコレクター
ガベージコレクタは、バックグラウンドで実行され、メモリを解放するコードです。ヒープにメモリを割り当てると、メモリリークと呼ばれる解放するのを忘れがちです。時間が経つにつれて、アプリケーションが消費するメモリは増大し、クラッシュするまで増大します。ガベージコレクターが定期的に不要になったメモリを解放すると、このクラスのバグを排除できます。もちろん、ガベージコレクターの処理速度が低下するため、これには代償が伴います。
静的およびスタックの問題は何ですか?
&quot; static&quot;の問題割り当てとは、コンパイル時に割り当てが行われることです。これを使用して、可変数のデータを割り当てることはできません。その数は実行時まで不明です。
&quot;スタック&quot;の割り当てに関する問題割り当てを行うサブルーチンが戻るとすぐに割り当てが破棄されることです。
ヒープに変数を割り当てずにアプリケーション全体を作成できますか?
おそらく、そうではありませんが、些細ではなく、通常の大きなアプリケーションではありません(ただし、いわゆる「埋め込み」プログラムは、C ++のサブセットを使用して、ヒープなしで記述される場合があります)。
ガベージコレクターの機能
データを監視(「マークアンドスイープ」)して、アプリケーションがデータを参照しなくなったことを検出します。これは、アプリケーションがデータの割り当てを解除する必要がないため、アプリケーションにとって便利です...しかし、ガベージコレクターは計算コストが高い可能性があります。
ガベージコレクターは、C ++プログラミングの通常の機能ではありません。
このガベージコレクターを使用して実行できなかったメモリを自分で操作できることは何ですか?
確定的なメモリ割り当て解除のためのC ++メカニズムを学習します。
- 'static':割り当て解除されない
- 'stack':変数が「範囲外になるとすぐに」
- 'heap':ポインターが削除されたとき(アプリケーションによって明示的に削除されたとき、または他のサブルーチン内で暗黙的に削除されたとき)
スタックのメモリ割り当て(関数変数、ローカル変数)は、スタックが「深い」場合に問題になる可能性があります。そして、スタック割り当てに使用可能なメモリをオーバーフローさせます。ヒープは、複数のスレッドから、またはプログラムのライフサイクル全体にアクセスする必要があるオブジェクト用です。ヒープを使用せずにプログラム全体を作成できます。
ガベージコレクタがなくてもメモリを簡単にリークできますが、オブジェクトとメモリがいつ解放されるかを指示することもできます。 GCを実行するときにJavaの問題に遭遇しましたが、リアルタイムプロセスがあります。これは、GCが排他スレッドであるためです(他には何も実行できません)。したがって、パフォーマンスが重要であり、リークされたオブジェクトがないことを保証できる場合、GCを使用しないことが非常に役立ちます。それ以外の場合は、アプリケーションがメモリを消費し、リークの原因を突き止める必要があるときに、あなたは人生を憎むだけです。
プログラムが事前に割り当てるメモリ量を知らない場合(スタック変数を使用できないため)。リンクリストと言うと、リストはそのサイズが何であるかを事前に知らなくても大きくなる可能性があります。そのため、リンクされたリストに挿入される要素の数を知らない場合、ヒープに割り当てることは意味があります。
一部の状況でのGCの利点は、他の状況での迷惑です。 GCへの依存は、GCについてあまり考えないことを奨励します。理論的には、「アイドル」期間または絶対に必要になるまで待機し、帯域幅を奪い、アプリの応答遅延を引き起こします。
しかし、「それについて考えない」必要はありません。マルチスレッドアプリの他のすべてと同様に、譲歩できる場合は譲歩できます。したがって、たとえば、.Netでは、GCを要求することができます。これを行うことにより、GCの実行頻度を低くする代わりに、GCの実行頻度を短くすることができ、このオーバーヘッドに関連する遅延を分散させることができます。
しかし、これはGCの主な魅力を打ち負かします。GCは、「自動であるため、あまり考える必要がないように奨励されているようです」
GCが普及する前にプログラミングに最初にさらされ、malloc / freeおよびnew / deleteに慣れていた場合、GCが少し面倒だったり、信頼できない(あるいはその両方である)場合もあります「最適化」には不信感があり、これには歴史があります。多くのアプリはランダムなレイテンシを許容します。ただし、ランダムレイテンシが許容されない場合、一般的な反応はGC環境を避け、純粋にアンマネージコード(または神の禁断、死にかけの芸術、アセンブリ言語)の方向に進むことです。
GCで離乳した夏休みの学生、インターン、賢い子供がここにいました。彼はGCの優位性に非常に敬の念を抱いていたため、アンマネージC / C ++でプログラミングする場合でも、malloc / free new / deleteモデルに従うことを拒否しました。これは、現代のプログラミング言語で行う必要がないためです。 &quot;あなたが知っています?小さくて実行時間の短いアプリの場合、実際にそれを回避できますが、実行時間の長いパフォーマンスアプリの場合はできません。
スタックは、プログラムがコンパイルされるたびにコンパイラによって割り当てられたメモリであり、デフォルトでは、コンパイラはOSからメモリを割り当てます(IDEのコンパイラ設定から設定を変更できます)、OSはメモリを提供するものです、それはシステム上の多くの利用可能なメモリと他の多くのものに依存し、それらがコピーする変数を宣言するときにスタックメモリに割り当てられます(形式として参照)それらの変数はデフォルトでいくつかの命名規則に従ってスタックにプッシュされ、そのCDECLビジュアルスタジオで 例:中置記法: c = a + b; スタックのプッシュは、右から左へのプッシュ、bからスタック、演算子、aからスタック、およびi、e cからスタックへの結果の順に実行されます。 修正前の表記では: = +タクシー ここでは、すべての変数が1番目(右から左)にスタックされてから、操作が行われます。 コンパイラによって割り当てられたこのメモリは修正されています。したがって、アプリケーションに1MBのメモリが割り当てられているとします。たとえば、変数が700kbのメモリを使用するとします(動的に割り当てられない限り、すべてのローカル変数はスタックにプッシュされます)。 そして、このスタックの寿命は短くなります。関数のスコープが終了すると、これらのスタックはクリアされます。