1

私は C++ にかなり慣れていないので、スタックとヒープの概念をできる限り (または少なくとも知る必要がある限り) 理解しようとしています。スターターはそれほど気にするべきではないと言う人もいますが、メモリ リークやスタック オーバーフローは非常に簡単に発生するように思えます。私はいくつかのものを読んでいますが、まだ少し混乱しており、それが正しいかどうか確信が持てません.

これが私がこれまでに得たものです...

1. ヒープ:

ヒープは、動的に割り当てられる共有領域です。適切なポインターとコンテンツ (タイプと長さ) の知識があれば、プロセスのどの部分からでもアクセスできます。間違ったポインター (単純なアドレスまたは割り当て解除へのポインター) を使用しようとすると、セグメンテーション違反が発生します。割り当てられたものよりも大きなコンテンツにアクセスすると、セグメンテーション違反が発生します (たとえば、割り当てられたものよりも大きな配列を読み取ろうとするなど)。未使用の領域は、メモリリークを避けるために「手動で」割り当てを解除する必要があります

2.スタック:

スタックは、パラメーターとローカル変数が割り当てられるメモリの一部です。スタックのサイズには制限があります。スタックは LIFO (Last In First Out) として機能します。

スタックが事前定義されたサイズ (スタック サイズ) のビンであるとしましょう。ローカル変数を定義すると、それらはスタック (ビン) に置かれ、スコープが変更されるとすぐに (関数が呼び出されるなど)、プラッターがビンで使用され、以前のスコープで定義された変数と新しいローカル変数へのアクセスが防止されます。スコープが作成されます。関数が終了すると、すべてのローカル変数が破棄され、ビン内のプラッターが削除されます (以前のスコープに戻ります)。

例 :

void MyFunction()
{
    int *HeapArray = new int[10];
    // HeapArray is assigned 40 bytes from the heap
    // *HeapArray is assigned 4 bytes from the stack in a 32 bit environment

    int StackArray1[10];
    // StackArray is assigned 40 bytes from the stack

    int StackArray2[20];
    // StackArray is assigned 80 bytes from the stack

    HeapArray = StackArray2;
    // segmentation fault because StackArray it too large

    delete HeapArray;
    // this will deallocate the area assigned in the heap
    // omitting delete would result in memory leaks
    // the pointer itself *HeapArray continues to exist in the stack

    HeapArray = StackArray1;
    // segmentation fault because HeapArray is pointing to deallocated memory

    MyFunction();
    // this will result in a stack overflow

}

質問:

Q1. スタックに対して大きすぎるローカル変数を定義したり、上記の例のように無限再帰関数を使用したりすると、セグメンテーション違反が発生します。これが「スタック オーバーフロー」と言っていないのはなぜですか? スタックが「ヒープにオーバーフロー」してセグメンテーション違反を引き起こしているためですか?

Q2. スタックに指定したビンとプラッターの例を想定すると、使用時にextern、コンテンツは最後のプラッターの上にある新しいスコープにコピーされますか、それとも何らかのポインターが作成されますか?

4

2 に答える 2

3

投稿したコードにはバグがたくさんありますが、コメントに記載されているものだけではありません。ちなみに、プレーンな C スタイルの配列は代入できません。したがって、次の行は右側の配列の内容を左側の配列にコピーしていません。

HeapArray = StackArray2;

C および C++ では、配列から配列の最初の要素を指すポインターへの暗黙的な変換が可能です。これは、最初の要素へのポインターへの減衰として一般に知られています。したがって、上記のステートメントにより、HeapArrayポインターは の先頭を指しますStackArray2。次に、 を呼び出すdeleteと、編集されていないメモリを使用HeapArrayしようとしています。これは未定義の動作であり、プログラムがクラッシュします (運が良ければ)。deletenew

newそれに加えて、そのメモリへの唯一のポインタを失ったため、経由で割り当てたメモリもリークしました。

同様に、次の へのHeapArray代入は のアドレスをStackArray1に代入しますHeapArray。ポインターを割り当てるだけなので、この行にエラーはありません。プログラムは引き続き正常に実行されます (ただし、以前の削除により既にクラッシュしている可能性があります)。


あなたの質問に答えるには -

1 - スタックのオーバーフローや誤った削除が常に予測可能な方法で失敗するという保証はありません。また、使用しているコンパイラにも依存します。内のすべてのコードをコメント アウトするMyFunction()と、それ自体への再帰呼び出しを除いて、g++ 4.8 は警告を発行せず、セグメンテーション エラーで失敗します。ただし、VS2012 は警告を発行します。

警告 C4717: 'MyFunction': すべての制御パスで再帰的です。関数はランタイム スタック オーバーフローを引き起こします

実行時に失敗します

Test.exe の 0x00062949 で未処理の例外: 0xC00000FD: スタック オーバーフロー (パラメーター: 0x00000001、0x00802FA4)

ただし、それは最適化を無効にした状態でした。最適化を有効にすると、プログラムは無限に実行されます (両方のコンパイラで)。これはおそらく、両方のコンパイラで、self への再帰呼び出しが無限ループに置き換えられたほど、コードが単純であるためです。これでスタック オーバーフローは発生しなくなりますが、プログラムが終了することもありません。

2 - グローバル変数 (extern別の翻訳単位からアクセスする変数) はスタックに保存されません。それらは、スタックとは異なるメモリの実装定義領域に格納されます。

于 2013-06-02T14:31:02.623 に答える
2

Q1. 何が起こるかはスタックオーバーフローと呼ばれますが、その結果、スタックに許可されているメモリの外に出ているため、アクセスできないものにアクセスしようとしているため、技術的にはセグメンテーション違反です。

Q2. スタックは以前のコンテンツを非表示にせず、スタックのより深いものを指すポインターは引き続き有効であり、連続する呼び出しによって非表示にならないため、例は適合しません。スタックのように動作するため (スタックの上にデータが割り当てられます)、スタックと呼ばれますが、下にあるものはすべて単なるメモリであり、ポインタを保持していれば合法的にアクセスできます。

追加のメモとして、コール スタックには関数呼び出しのアクティベーション レコードも含まれており、関数呼び出しから正しく戻るために使用されます。

于 2013-06-02T13:59:20.853 に答える