5

重複の可能性:
ランタイム例外、再帰が深すぎます

ac#.netプログラムの開発中に問題が発生し、単純な問題に単純化したので、次のような関数を呼び出すと、このコードがスタックオーバーフロー例外をスローする理由を理解する必要があります。

CheckFunc(16000);

でもこう呼べば大丈夫

CheckFunc(1000);

ここに関数があります:

private void CheckFunc(Int32 i)
{
    if (i == 0)
        MessageBox.Show("good");
    else
        CheckFunc(i - 1);
}

コードをできるだけ単純にしようとしました...

オーバーフローするスタックがあることは理解していますが、どのスタックですか?どうすればこれを修正できますか?

ありがとう。

4

7 に答える 7

12

問題は確かにスタックオーバーフローです。

なぜそれが起こるのか:

スタックは特別なメモリ領域であり、いくつかのものが格納されています。

  • 関数のローカル変数
  • 関数パラメーター
  • (最も重要な)関数の差出人アドレス。関数から戻るとき、これはプロセッサがどこに戻るかを知る方法です。

問題は、このメモリ領域が制限されていることです。再帰呼び出しは、このスタックに大量のデータを追加し、すぐにいっぱいにします。

それを修正する方法:

いくつかの方法があります。

  • 変数の数を減らします。再帰関数にローカル配列がある場合は、問題を探しています。
  • 関数のパラメーターの数を減らします(明らかにここではそうではありません)
  • 再帰呼び出しの数を減らします。

これが十分でない場合、唯一の解決策は反復的な解決策を見つけることです。

于 2012-07-03T13:12:52.537 に答える
11

これは、16000回繰り返すのに十分なスタックスペースがないためです。

再帰は、ほとんどの場合、それよりもはるかに低いレベルになるはずです。それ以外の場合は、ループとして書き直します。これを他の方法で修正することはできません。

于 2012-07-03T13:10:47.860 に答える
2

スタックと、それがプログラムの実行でどのように使用されるかについて読む必要があります。一言で言えば、関数は自分自身を何度も再帰的に呼び出すために失敗します。スタックは、ヒープと同様に有限のサイズですが、ヒープとは異なり、一般的にはるかに小さくなります。関数がそれ自体を呼び出すたびに、スタック内の一部のメモリが、関数呼び出しと関数にローカルな変数(例では変数iへの参照)に関する情報を保持するために使用されます。これはスタックフレームと呼ばれます。再帰が深すぎるために作成されるスタックフレームが多すぎると、スタックオーバーフローが発生します。

CheckFuncで再帰を削除し、代わりにループから呼び出す必要があります。

于 2012-07-03T13:19:12.600 に答える
0

C#ではコールスタックに制限があります。そして最初のケースでは、その数を超えたため、StackOverflowExceptionが発生します

これを修正する方法はありませんが、再帰の深さを減らすことで、自然にそれを回避できます。

于 2012-07-03T13:10:53.630 に答える
0

私はそれを繰り返し行いたいと思います。uに16000の再帰ステップがある場合、それは非常に遅くなります(私は思います)。

于 2012-07-03T13:12:32.707 に答える
0

コールスタックがオーバーフローします。

関数(またはプログラミング言語で「サブルーチン」、「メソッド」などと呼ばれるもの)を呼び出すたびに、CPUは関数の終了後に戻らなければならない場所のアドレスを格納します。さらに、「ローカル」変数またはパラメーターは通常、スタックにも格納されます。

そのスタックのサイズは固定されており、通常は魔法を使って増やすことができます。スレッドを作成するときは通常、スタックサイズを指定できます。プログラムをリンクするときに、スタックサイズを設定するリンカーオプションもある場合があります。

基本的に、コードはスタック上に16000個の「i」のコピーと16000個の戻りポインターを作成し、さらにコンパイラーが関数呼び出しでスタックに格納する可能性のあるものをすべて作成します。他の試みでは、これらのもののコピーを1000個しか作成しませんでした。

もちろん、最適化する必要がある「末尾再帰」と呼ばれるものを実行しています。なぜあなたのコンパイラがそうしないのか私に聞かないでください。

于 2012-07-03T13:19:23.973 に答える
0

これは基本的に、再帰の使用が間違っているためです。

オーバーフローしたスタックは、プロセスの呼び出しスタックです。スタックは、メソッドに送信されるパラメーター、呼び出しの戻りアドレス、およびメソッド内のローカル変数に使用されます。

呼び出しごとに、これがスタックに追加されます。

+---------------------+
|                     |
         ...
|                     |
+---------------------+  <-- stack pointer before call
| parameter: int      |
+---------------------+
| return address      |
+---------------------+
| stack frame for     |
|   local variables   |
+---------------------+  <-- stack pointer in the call

再帰呼び出しごとにスタックに別のチャンクが追加されます。スタックスペースが制限されているため(2 MB IIRCに)、数千レベルの深さの再帰を実行すると、スタックがいっぱいになります。

再帰を適切に使用する場合は、データを分割して半分に処理するようにします。単一の部分を削り取るのではありません。基本的に:

private void CheckFunc(int i) {
  if (i == 0) {
    MessageBox.Show("good");
  } else {
    CheckFunc(i / 2);
  }
}

この再帰は、どの整数値でも31レベルより深くなることはありません。

于 2012-07-03T13:31:20.067 に答える