C++ におけるヒープとスタックの違い

C++ では、メモリはヒープ、スタック、空き記憶領域、グローバル/静的記憶領域、定数記憶領域の 5 つの領域に分割されます。

1.スタック

スタックは、必要に応じてコンパイラによって割り当てられ、不要になると自動的にクリアされる変数の記憶領域です。内部の変数は通常、ローカル変数、関数パラメータなどです。

2. ヒープ

ヒープは new によって割り当てられたメモリ ブロックです。リリース コンパイラはそれらを考慮せず、アプリケーションによって制御されます。一般に、new は削除に対応します。プログラマがそれを解放しない場合、プログラムの終了後にオペレーティング システムが自動的にそれをリサイクルします。

3. 無料のストレージ領域

空きストレージ領域は、malloc() などによって割り当てられたメモリ ブロックです。これはヒープと非常によく似ていますが、寿命を終えるために空きを使用します。

4. グローバル/静的ストレージ領域

グローバル/静的記憶領域、グローバル変数と静的変数は同じメモリブロックに割り当てられます。以前の C 言語では、グローバル変数は初期化済みと未初期化に分かれていました(初期化済みのグローバル変数と静的変数が 1 つの領域にあり、初期化されていないグローバル変数)静的変数は互いに隣接する別の領域にあり、初期化されていないオブジェクト記憶領域は void* を介してアクセスおよび操作でき、プログラム終了後にシステムによって解放されます)。C++ ではそのような区別はなく、これらは共有されます。同じメモリ領域です。

5. 常時記憶領域

定数記憶領域。これは定数を格納する比較的特殊な記憶領域であり、変更は許可されていません(もちろん、不正な手段で変更することは可能であり、方法は多数あります)

ヒープとスタックの明確な区別

//メイン.cpp

inta=0; //グローバル初期化領域

char*p1; //グローバル未初期化領域

主要()

{

intb; // スタック

chars[] = "abc"; // スタック

char*p2; //スタック

char*p3="123456"; //123456 は定数領域にあり、p3 はスタック上にあります。

staticintc=0; //グローバル(静的)初期化領域

p1= (char*)malloc(10);

p2= (char*)malloc(20); //10、20バイトの確保領域はヒープ領域にあります。

strcpy(p1, "123456"); //123456 は定数領域に配置され、コンパイラはそれと p3 が指す "123456" を 1 か所にまとめて最適化する可能性があります。

}

ヒープ領域とスタック領域に格納されます。呼び出された関数が有効な場合、関数内の関連する非静的変数またはポインタが格納されます。malloc によって割り当てられたメモリ空間はヒープ領域に配置されますが、ポインタは返されます。 malloc割り当て後はスタック領域にあります。

ヒープとスタックの理論的知識

スタック stack: システムによって自動的に割り当てられます。たとえば、関数内でローカル変数 int b を宣言すると、システムがスタック内に b 用のスペースを自動的に作成します。

ヒープ heap: プログラマが申請してサイズを指定する必要があります。C では p1 = (char *)malloc(10) などの malloc 関数、C++ では p2 = (char *)malloc などの new 演算子を使用します。 (10); ただし、p1 と p2 自体がスタック上にあることに注意してください。

voidf()

{

int*p=newint[5];

}

この短いコードにはヒープとスタックが含まれています。new を見ると、まずヒープ メモリを割り当てたと考える必要があります。では、ポインタ p はどうなるのでしょうか? 彼はスタック メモリの一部を割り当てたので、この文の意味は次のようになります。ヒープ メモリの一部を指すポインタ p がスタック メモリに格納されます。プログラムはまずヒープ内に割り当てられたメモリのサイズを決定し、次に演算子 new を呼び出してメモリを割り当て、次にメモリの最初のアドレスを返してスタックに置きます。VC6 での逆アセンブリ コードは次のとおりです。

00401028 プッシュ 14 時間

0040102A 電話オペレーター新規 (00401060)

0040102F 追加 esp,4

00401032 mov dwordptr [ebp-8],eax

00401035 mov eax,dword ptr [ebp-8]

00401038 mov dwordptr [ebp-4],eax

ここでは簡単にするためにメモリを解放しませんでしたが、どのように解放すればよいでしょうか。p削除ですか?間違っています。delete []p にする必要があります。これはコンパイラに、削除するのは配列であり、VC6 は対応する Cookie 情報に従ってメモリを解放することを伝えるためです。さて、本題に戻りましょう。ヒープとスタックの違いは何でしょうか? 主な違いは次のとおりです。

1. 管理方法が異なる。

管理方法: スタックの場合は、手動制御なしでコンパイラによって自動的に管理されますが、ヒープの場合は、メモリ リークが発生しやすいリリース作業がプログラマによって制御されます。

2. 空間のサイズが異なります。

スペース サイズ: 一般に、32 ビット システムでは、ヒープ メモリは 4 G に達します。この観点から見ると、ヒープ メモリにはほとんど制限がありません。ただし、スタックには一般に一定のスペース サイズがあり、たとえば VC6 では、デフォルトのスタック スペース サイズは 1 M です (覚えていませんが、そうだったような気がします)。もちろん、変更することもできます。プロジェクトを開き、プロジェクト -> 設定 -> リンクとメニューを操作し、カテゴリで出力を選択し、スタックの最大値を設定し、予約にコミットします。注: 予約の最小値は 4 バイトです。コミットは仮想メモリのページ ファイルで予約されます。これより大きく設定すると、スタックがより大きな値を開くことになり、メモリのオーバーヘッドと起動時間が増加する可能性があります。

3. フラグメントを生成できるかどうかが異なります。

ヒープの場合、新規/削除が頻繁に行われると必然的にメモリ空間に不連続性が生じ、その結果、多数のフラグメントが発生し、プログラムの効率が低下します。スタックの場合、この問題は存在しません。スタックは先入れ後出しキューであり、それらは 1 対 1 に対応しているため、メモリ ブロックがメモリ ブロックの中央からポップすることは決してありません。 stack. その上の後方スタックの内容がポップアップ表示されています。詳細については、データ構造を参照してください。ここでは 1 つずつ説明しません。

4. 異なる成長方向。

成長方向: ヒープの場合、成長方向は上向き、つまりメモリ アドレスが増加する方向であり、スタックの場合、成長方向は下向き、つまりメモリ アドレスが減少する方向です。

5. さまざまな配布方法。

ヒープは動的に割り当てられ、静的に割り当てられるヒープはありません。スタックを割り当てるには、静的割り当てと動的割り当ての 2 つの方法があります。静的割り当ては、ローカル変数の割り当てと同様に、コンパイラによって実行されます。動的割り当てはalloca関数によって割り当てられますが、スタックの動的割り当てはヒープとは異なり、手動で実装することなくコンパイラによって動的割り当てが解放されます。

6. 分配効率が異なります。

スタックはマシン システムによって提供されるデータ構造です。コンピュータは最下層でスタックのサポートを提供します。スタックのアドレスを格納する特別なレジスタを割り当てます。また、スタックをプッシュおよびポップするための特別な命令があります。スタックの効率が比較的高いと判断されます。ヒープは C/C++ 関数ライブラリによって提供されますが、その仕組みは非常に複雑です。たとえば、メモリのブロックを割り当てるために、ライブラリ関数は十分な領域がない場合、使用可能なヒープ メモリを検索します (おそらく原因)メモリの断片化が多すぎる場合)、システム関数を呼び出してプログラム データ セグメントのメモリ領域を増やすことができるため、十分なメモリを割り当ててからリターンを実行することができます。明らかに、ヒープはスタックよりも効率がはるかに低くなります。

まとめると、スタックと比較すると、新規/削除が大量に行われるため、大量のメモリ断片化が発生しやすく、特別なシステムサポートがないため、効率が非常に低い、ユーザー モードとコア モード、メモリ アプリケーション間の切り替えの可能性は、より高価になります。そのため、プログラム内で最も広く使われているのがスタックであり、関数呼び出しもスタックを使用して行われ、関数呼び出し処理のパラメータ、リターンアドレス、EBP、ローカル変数などはスタックに格納されます。したがって、できる限りヒープではなくスタックを使用することをお勧めします。スタックには非常に多くの利点がありますが、ヒープに比べて柔軟性が低いため、大量のメモリ領域を割り当てるにはヒープを使用した方が良い場合があります。

ヒープであってもスタックであっても、境界外現象の発生を防ぐ必要があります (意図的に境界外にしない限り)。境界外の結果はプログラムがクラッシュするか、またはクラッシュするかのどちらかです。プログラムのヒープ構造とスタック構造が破壊され、予期しない結果が生じます。プログラムの実行中に上記の問題が発生しない場合でも、注意が必要です。プログラムはいつでもクラッシュする可能性があります。その時点でデバッグするのは非常に困難です:)誰かがスタックをまとめた場合、それはヒープではなくスタックを意味します、ははは!

static は変数の保存と可視性を制御するために使用されます。

関数内で定義された変数。プログラムがその定義に従って実行されると、コンパイラはスタック上にその変数用のスペースを割り当てます。また、関数によってスタック上に割り当てられたスペースは、関数の実行の終了時に解放されるため、問題が発生します。 : この変数の値を次の呼び出しまで関数に保存したい場合、どうすればよいでしょうか? 最も簡単に考える方法は、グローバル変数を定義することですが、グローバル変数として定義すると多くの欠点があります。最も明らかな欠点は、この変数のアクセス スコープが破壊されることです (そのため、この関数で定義された変数は無効になります)。この関数によってのみ制御されます)。データ オブジェクトは、特定のオブジェクトではなくクラス全体にサービスを提供する必要があり、同時にクラスのカプセル化を破壊しないように努める必要があります。つまり、このメンバーはクラス内に隠され、外部からは見えないようにする必要があります。 。静的を使用して目標を達成できます。

静電気の内部メカニズム

プログラムの実行開始時には、静的データ メンバーが存在している必要があります。関数はプログラムの実行中に呼び出されるため、関数内で静的データ メンバーを割り当てたり初期化したりすることはできません。このように、スペース割り当てには 3 つの場所が考えられます。1 つはクラス宣言があるクラスの外部インターフェイスのヘッダー ファイル、2 つ目はクラス定義の内部実装でクラス メンバー関数定義がある場所です。 ; 3 番目は、関数の前の application () グローバル データの宣言と定義の main です。静的データ メンバーは実際に領域を割り当てる必要があるため、クラス宣言で定義できません (宣言できるのはデータ メンバーのみです)。クラス宣言はクラスの「サイズと仕様」を宣言するだけで、実際のメモリ割り当ては行わないため、クラス宣言に定義を記述するのは誤りです。また、ヘッダー ファイルのクラス宣言の外で定義することもできません。そのクラスを使用する複数のソース ファイルで繰り返し定義されることになるためです。static は、スタック上の領域ではなく、プログラムの静的記憶領域に変数を格納するようにコンパイラに指示するために導入されています。静的データ メンバーは、定義された順序で初期化されます。静的メンバーがネストされている場合、ネストされたメンバーが初期化されていることを確認する必要があります。削除の順序は初期化の逆の順序です。

静的の利点

すべてのオブジェクトに共通であるため、メモリを節約できます。したがって、複数のオブジェクトの場合、静的データ メンバーは 1 か所にのみ保存され、すべてのオブジェクトで共有されます。静的データ メンバーの値は各オブジェクトで同じですが、その値は更新できます。静的データ メンバーの値が一度更新される限り、すべてのオブジェクトは同じ更新された値にアクセスすることが保証されるため、時間効率が向上します。静的データ メンバーを参照する場合は、次の形式を使用します。 <クラス名>::<静的メンバー名> 静的データ メンバーのアクセス権が許可されている場合 (つまり、パブリック メンバー)、プログラム内で静的データ メンバーを参照できます。上記の形式に従ってください。

(1) クラスの静的メンバー関数は、クラスではなくクラス全体に属するオブジェクトであるため、 this ポインターを持たず、クラスの静的データと静的メンバー関数にのみアクセスします。(2) 静的メンバ関数を仮想関数として定義することはできません。(3) 静的メンバはクラス内で宣言されクラス外で動作するため、そのアドレスを取得する動作がやや特殊で、変数アドレスはそのデータ型へのポインタ、関数アドレス型は「非メンバ関数ポインタ」となります。 。(4) 静的メンバー関数には this ポインターがないため、非メンバー関数とほぼ同等であり、その結果、予期せぬ利点があります。コールバック関数になり、C++ と C ベースの X ウィンドウ システムを組み合わせることができます。同時にスレッド関数に正常に適用されました。(5) static はプログラムの時間と空間のオーバーヘッドを増加させません。それどころか、親クラスの静的メンバーへのサブクラスのアクセス時間を短縮し、サブクラスのメモリ空間を節約します。(6) 静的データ メンバーの場合は、<定義または説明> の前にキーワード static を追加します。(7) 静的データメンバは静的に格納されるため、初期化が必要です。(8) 静的メンバーの初期化は、一般的なデータ メンバーの初期化とは異なります: 初期化はクラスの外側で実行され、一般的な静的変数やオブジェクトとの混同を避けるために static は前に追加されません。このためのアクセス制御シンボル private、public などメンバーは初期化中に追加されません。スコープ演算子は、初期化中に所属するクラスを示すために使用されます。そのため、静的データ メンバーの初期化形式を取得します: <データ型><クラス名>::<静的データ メンバー名>= <value> (9) 親クラスの影響を防ぐため、サブクラス内に親クラスと同じ静的変数を定義することで、親クラスの影響を防ぐことができます。ここで注意すべき点が 1 つあります。静的メンバーは親クラスとサブクラスで共有されると言っていますが、静的メンバーを繰り返し定義しています。これによりエラーが発生しますか? いいえ、私たちのコンパイラは、名前をマングリングして一意のフラグを生成するという巧妙なトリックを使用しています。

グローバル変数 静的変数

C 言語では、static として宣言された変数には 2 つの特徴があります。

  1. 変数はプログラムのグローバル ストレージ領域に配置されるため、次の呼び出し時に元の割り当てが維持されます。これがスタック変数やヒープ変数との違いです。

  1. 変数は static を使用して、変数のスコープ内でのみ表示されることをコンパイラーに伝えます。これがグローバル変数との違いです。ヒント: A. グローバル変数が 1 つの C ファイル内でのみアクセスされる場合は、この変数を静的グローバル変数に変更してモジュール間の結合を減らすことができます; B. グローバル変数が 1 つの関数によってのみアクセスされる場合は、この変数を関数の静的ローカル変数に変更して、モジュール間の結合を減らす C. 動的グローバル変数、静的グローバル変数、および静的ローカル変数にアクセスする関数を設計および使用する場合、再入可能性の問題を考慮する必要があります。 D. リエントラント関数が必要な場合は、関数内で静的変数の使用を避けなければなりません (そのような関数は、「内部メモリ」関数を備えた関数と呼ばれます) E. 静的変数は関数内で使用する必要があります。たとえば、関数が戻り値がポインタ型の場合は静的ローカル変数のアドレスを戻り値とし、auto型の場合はエラーポインタを返します。関数の前に static を追加すると、その関数は静的関数になります。ただし、ここでの「静的」の意味は、保存方法を指すのではなく、関数の範囲がこのファイルに限定されることを指します(したがって、内部関数とも呼ばれます)。内部関数を使用する利点は、異なる人が異なる関数を作成するときに、自分が定義した関数が他のファイル内の関数と同じ名前を持つかどうかを心配する必要がないことです。拡張分析: static という用語には特殊な歴史があり、もともと static というキーワードは、ブロックを抜けた後に持続するローカル変数を表すために C で導入されました。その後、C では static に 2 番目の意味があり、他のファイルからアクセスできないグローバル変数や関数を表すために使用されます。新しいキーワードの導入を避けるために、この 2 番目の意味を表すために静的キーワードが引き続き使用されます。最後に、C++ はこのキーワードを再利用し、以前とは異なる 3 番目の意味を与えました。つまり、クラスに属する特定のオブジェクトではなく、クラスに属する変数と関数を示すためです (Java のこのキーワードと同じ意味)。

グローバル変数、静的グローバル変数、静的ローカル変数、ローカル変数の違い

変数は、グローバル変数、静的グローバル変数、静的ローカル変数、ローカル変数に分類できます。記憶領域に応じて、グローバル変数、静的グローバル変数、静的ローカル変数はすべてメモリの静的記憶領域に格納され、ローカル変数はメモリのスタック領域に格納されます。スコープに関しては、グローバル変数はプロジェクト ファイル全体で有効です。静的グローバル変数は、それが定義されているファイルでのみ有効です。静的ローカル変数は、それが定義されている関数でのみ有効ですが、プログラムは割り当てのみを行います。ローカル変数は、それが定義された関数内では有効ですが、関数が戻った後は無効になります。グローバル変数 (外部変数) の記述の前に static を付けて、静的グローバル変数を構成します。グローバル変数自体は静的格納メソッドであり、静的グローバル変数も当然静的格納メソッドです。どちらも保存方法に違いはありません。両者の違いは、非静的グローバル変数の有効範囲はソースプログラム全体であり、ソースプログラムが複数のソースファイルで構成されている場合、非静的グローバル変数は各ソースファイル内で有効となります。静的グローバル変数はスコープを制限します。つまり、変数が定義されているソース ファイル内でのみ有効であり、同じソース プログラムの他のソース ファイルでは使用できません。静的グローバル変数のスコープは 1 つのソース ファイルに制限されているため、このソース ファイル内の関数によってのみ共有できるため、他のソース ファイルでエラーが発生することを回避できます。上記の分析から、ローカル変数を静的変数に変更すると、その保存方法が変更される、つまり、その有効期間が変更されることがわかります。グローバル変数を静的変数に変更すると、そのスコープが変更され、その使用範囲が制限されます。静的関数のスコープは通常の関数とは異なります。この文書内のみ。現在のソースファイル内でのみ使用される関数は内部関数(static)として記述し、内部関数は現在のソースファイル内に記述して定義する必要があります。現在のソース ファイルの外部で使用できる関数については、これらの関数を使用するソース ファイルにこのヘッダー ファイルが含まれている必要があることをヘッダー ファイルに記述する必要があります。 静的グローバル変数と通常のグローバル変数の違いは何ですか: 静的グローバル変数は次のとおりです。他のファイル単位で参照されるのを防ぐため、一度だけ初期化されます。静的ローカル変数と通常のローカル変数の違いは何ですか: 静的ローカル変数は 1 回だけ初期化され、次回は最後の結果値に基づきます。違いは何ですか静的関数と通常の関数の間: 静的関数はメモリ内にコピーが 1 つだけあり、通常の関数は呼び出しごとにコピーを保持します。

グローバル変数と静的変数は、手動で初期化されない場合、コンパイラによって 0 に初期化されます。ローカル変数の値は不明です。

おすすめ

転載: blog.csdn.net/allexw/article/details/129067728