175

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

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

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

誰かがこの宣言で私に言ったことがあります:

int * asafe=new int;

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

asafe=new int;

?

4

9 に答える 9

237

同様の質問がありましたが、統計については質問されていませんでした。

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

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

  • ヒープは、動的に使用できるメモリの集まりです。オブジェクトに 4kb が必要な場合、動的アロケータはヒープ内の空き領域のリストを調べ、4kb のチャンクを選択して提供します。一般に、動的メモリ アロケータ (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 などはスクリプティングには問題ありませんが、メインのゲーム エンジンには適していません。

于 2009-01-03T14:08:09.277 に答える
57

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

さて、あなたが参照する 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 つの名前のアドレスはすべて同じで、すべて同じオブジェクトを示します。静的オブジェクトがいつどのように初期化されるかについては特別なルールがありますが、今は気にしないでください。これは、静的初期化順序の大失敗という用語によって意味されます。


動的保存期間

最後の保存期間は動的です。オブジェクトを別の島に置きたい場合や、オブジェクトを参照するポインタを配置したい場合に使用します。また、オブジェクトが大きい場合や、実行時にしか分からないサイズの配列を作成したい場合にも使用します。この柔軟性のために、動的ストレージ期間を持つオブジェクトは複雑で、管理が遅くなります。その動的期間を持つオブジェクトは、適切な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 のリファレンスについては、boost ドキュメントを参照してください: http://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm

于 2009-01-03T06:12:01.470 に答える
5

衒学者の 1 人がすぐにより良い答えを思いつくと確信していますが、主な違いは速度とサイズです。

スタック

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

ヒープ

割り当てが劇的に遅くなります。しかし、あなたは遊び、指し示すGBを持っています。

ガベージコレクター

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

于 2009-01-03T06:06:29.673 に答える
3

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

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

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

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

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

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

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

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

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

確定的なメモリ割り当て解除のための C++ メカニズムを学びます。

  • 「静的」: 決して解放されません
  • 'stack': 変数が「範囲外」になるとすぐに
  • 「ヒープ」: ポインターが削除されたとき (アプリケーションによって明示的に削除されたか、何らかのサブルーチン内で暗黙的に削除された)
于 2009-01-03T06:12:21.110 に答える
1

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

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

于 2009-01-03T06:09:58.293 に答える
1

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

于 2009-01-03T06:36:24.260 に答える
0

状況によっては GC の利点が、別の状況では煩わしくなります。GC への依存は、それについてあまり考えないことを奨励します。理論的には、「アイドル」期間まで、または絶対に必要になるまで待機します。これにより、帯域幅が盗まれ、アプリで応答の遅延が発生します。

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

しかし、これは GC の主な魅力を台無しにしてしまいます。GC は「オートマティックであるため、あまり考えなくてもよい」ように思われます。

GC が普及する前に初めてプログラミングに触れ、malloc/free と new/delete に慣れていた場合は、GC が少し面倒だと感じたり、不信感を抱いたりする可能性があります ('多くのアプリはランダムなレイテンシーを許容します。しかし、そうでないアプリでは、ランダムなレイテンシーがあまり受け入れられないため、GC 環境を避け、完全にアンマネージ コードの方向に進むのが一般的な反応です (または、神が禁じている、長く死につつある芸術、アセンブリ言語)。

しばらく前にここに夏の学生がいて、GCで離乳したインターンの賢い子供でした。彼は GC の優位性に非常に固執していたので、アンマネージ C/C++ でプログラミングするときでさえ、malloc/free new/delete モデルに従うことを拒否しました。あなたが知っています?実行時間の短い小さなアプリの場合は、実際にそれを回避できますが、実行時間の長い高性能アプリの場合はそうではありません。

于 2013-05-22T13:06:52.037 に答える
0

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

于 2013-11-06T04:50:32.443 に答える