• ベストアンサー

VC++ 再帰呼び出しについて

VC++6.0にてプログラミングを行っているものですが、 関数の再帰呼び出しについて質問です。 再帰呼び出しの際にスタックに積まれる変数というのは、 再帰呼び出しをする関数に渡す引数のことですか? スタックオーバーフローを起こさないために、 staticなポインタにHeap領域上の 変数を割り当てるとよい。 と分かったのですが、 この意味は、例えば static int *a = new int; ということなのですか?

質問者が選んだベストアンサー

  • ベストアンサー
  • isobeman
  • ベストアンサー率41% (17/41)
回答No.3

再帰では、複雑な処理を簡潔に書けて、良いように思えますが、スタックオーバーフローの危険性を常に考慮すべきです。 スタック領域を消費するのは、ローカル変数の他、関数の引数、戻り値等です。また、ポインタ変数もスタックを使用しますので、いくら使用するメモリを動的に割り当てたとしても、ポインタ変数分のスタックメモリ(4バイト)は消費されます。 スタックオーバーフローを起こさないためには、再帰呼び出しの回数を制限するなどする必要がありますが、制限してしまっては、処理が完結しないこともあります。 このような場合には、再帰以外の方法を検討する必要があります。 forやwhileループを使用して、同じ処理が実現できないか検討してみましょう。 staticなポインタに動的にメモリを割り当てる方法を使用すれば、有る程度スタックオーバーフローの発生を遅らせることができますが、気をつけなければならないことがあります。 static変数は、その関数内でいつでも同じメモリ領域を使用するようになりますので、以下のようにする必要があります。 // 良い例 void func() { static int* i = new int; // ここで何か処理をする delete i; func(); } 以下のようにすると、駄目です。 deleteで削除されるべきアドレスが、再帰先で上書きされてしまうためです。 // 駄目な例 void func() { static int* i = new int; // ここで何か処理をする func(); delete i; } また、こうしたところで、関数の呼び出しには、必ずスタックが消費されますので、完全にスタックオーバーフローを阻止することはできません。 再帰を使用しないアルゴリズムを考えましょう。

その他の回答 (3)

  • isobeman
  • ベストアンサー率41% (17/41)
回答No.4

↓に追加です。 再帰関数に引数をつけているなら、引数をやめてしまって、static変数で次の再帰関数に値を伝える方法もできますね。通常の引数とはちょっと勝手が違いますが、スタックオーバーフローは延期できると思います。 また、戻り値も同様に、voidにして戻り値用のstatic変数を用意すればよいと思います。 ただ、staticは、通常のローカル変数とは勝手が違いますから、注意が必要です。 ちなみに、この場合、再帰に突入するコールで引数を渡せない&戻り値を受け取れない、という問題が発生します。なので、もしやるとすると、モジュールレベル変数を使用した方がよいかもしれません。 、、、やればやるほど複雑怪奇になり、「再帰=きれいに書ける」の定理が崩れてきますので、再帰をなくす方向がおすすめです。

mkmarimo
質問者

お礼

回答ありがとうございます。 私の知りたいことが簡潔に書かれていて大変参考になりました。 再帰を用いないプログラムを考え直すことにします。 ありがとうございました。

noname#30727
noname#30727
回答No.2

記憶が曖昧ですが、VC++6.0の生成するスタックチェックコードは、一度に64KB以上くらいの大きさを確保すると、エラーを出すようになっていたはずです。 OS依存だと思いますが、それより小さいサイズを確保していくぶんには、デフォルトのスタック(1MB程度?)を使い切ったとしてもヒープと同じように自動的にサイズが増えていくので問題になることは少ないでしょう。 再帰かどうかは関係ないですが、関数を呼び出したときに、引数、ベースポインタ、戻りアドレス、呼び出し先関数の自動変数の合計サイズぶんのスタックが使用されます。普通に数十レベルくらいネストしても気にする程ではありません。 void recursiveFunction(int n) { char buff[256]; if (n < 100) { recursiveFunction(n + 1); } } int main() { recursiveFunction(0); return 0; } ↑くらいのネストなら「深刻」とは言えないです。 もっと巨大な領域を確保する必要があるなら、 struct MyDataType { char c[10000]; int n[1000]; double f[2000]; }; void recursiveFunction(int n) { MyDataType *p = new MyDataType; if (n < 100) { recursiveFunction(n + 1); } delete p; } ↑のようにnewを使えばいいと思います。

mkmarimo
質問者

補足

ご回答ありがとうございます。 上のnewを用いたプログラムでは、 MyDataType型のポインタpは使われているのですか? 私も上記のようなnewを用いた再帰関数を試してみたのですが、 やはりスタックオーバーフローで止まってしまいました。 やはり、ネストが深すぎるのでしょうか?

回答No.1

こんにちは。  関数で使うローカルな変数は、別の関数を呼び出すときにすべてスタックに積まれます。つまり、ローカル変数が多くて(領域が大きければなお効果的)、関数呼び出しのネストが深いと、再帰呼び出しでなくてもスタックオーバーフローをおこします。再帰呼び出しでは、気をつけないと呼び出しが何重にもなり、スタックオーバーフローをおこしやすい、ということです。  このことは、人間が処理の流れを目で追いかけることに喩えるとよくわかると思います。あなたの頭は、いくつの呼び出しを覚えていられますか?覚えられなくなった時点であなたのスタックがオーバーフローしたことになります。  関数中でstatic宣言すると、次の呼び出しで以前の内容は消えてしまいます。 main() { recallFunc(); } recallFunc() { static int i = 1; printf("i = %d\n", i++); recallFunc(); } さて、結果はどうなるでしょう?

mkmarimo
質問者

補足

回答ありがとうございます。 なるほど、おそらく理解しました。 staticな変数を用いた再帰呼び出しでは、 処理がうまく行えないですね。 Heap領域のみを使えば問題ないのでしょうか? それでもやはり、ネストが深いとスタックオーバーフローになってしまうんでしょうか?

関連するQ&A

  • 再帰呼び出しになってしまうのをループの形にしたい

    VBで繰り返して実行するプログラムを作ったのですが、 スタック領域をオーバーしてしまいます。 再帰呼び出しになっているのはわかったのですが、 改善ができません。 簡略したら下記のような状態でした。 Sub test1()  test2 End Sub Sub test2()  test1 End Sub このtest1を実行して、繰り返し作動するようにしたのですが、 当然スタックオーバーフローになります。 簡単にループに変形できるのでしょうか?

  • ストアドファンクションの再帰呼び出しについて

    「ストアドファンクションの再帰呼び出し」はできないのでしょうか。 hoge関数をhoge関数の中で呼び出しています。 hoge関数では、条件に当てはまらないと第三引数を、 編集して再帰呼出しをしています。 再帰呼出しする条件を引数に設定して実行すると、以下の実行結果(ERROR)が表示されます。 <実行結果> mysql> select hoge('7','1234','1234567891012'); ERROR 1424 (HY000): Recursive stored functions and triggers are not allowed. よろしくお願いします。

    • ベストアンサー
    • MySQL
  • 再帰呼び出しについて(基本)

    #include <stdio.h> void dan(int i); void kuku(void); void dan(int i) { int j; for (j = 1; j <= 9; j++) printf("%3d", i*j); putchar('\n'); } void kuku(void) { int i; for (i = 1; i <= 9; i++) dan(i); } int main(void) { kuku( ); return(0); } というプログラムがあるのですが、danとkukuを再帰呼び出しにしたいのですが、再帰の仕方がまったく分かりません。 知り合いに聞くと、両関数の引数を1つずつ増やすとよいと言われたのですが、手をつけられない状態です。 よろしくご教授お願いします。

  • 再帰呼び出しの計算量

    再帰呼び出しを用いた関数の計算量を求める方法がわからないので質問させていただきます. xのn乗を再帰呼び出しを用いて求める関数に関して,計算量を求める問題なのですが,どのような方針で求めればよいのでしょうか? int exponent(int x, int n) {   if(n == 0){     return 1;   }else{     return x * exponent(x, n-1);   } } exponentがn回呼ばれるからO(n)というのは間違いでしょうか?

  • バッファオーバーフローで疑問があります。

    スタックのバッファオーバーフローで、バッファオーバーフローを起こすことで、変数領域の内容が上書きされ、さらにスタックのその他の領域(サブルーチンの戻りアドレスなど)までも書き換わってしまうとありますが、なぜそんなことが起きるのでしょうか? 私のイメージ的に、スタックの内容が以下のようだとして、 |          | | 変数領域(B) | |------------| | 変数領域(A) | |------------| | SFP       | |------------| | 戻りアドレス | |------------| | 関数の引数  | |------------| バッファオーバーフローが起きたとしても上書きされるのは 変数領域A,Bだと思ったのですが、スタックには最大領域でも決まっていてそれおも超えるデータ量の場合は、領域いっぱいまですべて上書くようにでもなっているのでしょうか? 気になって仕方がありません。 どうかよろしくお願いいたします。

  • 再帰関数を使うとき、ソフトウェアスタックはハードウェアスタックと比べてどれくらい遅い?

    再帰を使って関数を書こうと思うのですが、再帰する回数が不明なので、スタックオーバーフローを避けるためソフトウェアスタックを使おうと思っている者です。 ソフトウェアスタックはどれくらい遅いのでしょうか? どう実装すれば速度が改善されるのでしょうか?

  • 再帰呼びだし

    再帰呼びだし 問題1 再帰呼び出しを用いてint型の配列({-9、8、-7、6、5、4、1、3、6、9、2、-14})の最小値を求めて出力するプログラムを作成せよ。関数名はmin_of_arrayとする。 問題2 同じく再帰呼び出しを用いてint型の配列({3、4、1、5、2})を小さい方から順番に求めて出力するプログラムを作成せよ。 再帰呼び出しが苦手で、じっくり解いていこうと思ったのですが、他の課題もあって、もう提出期限いっぱいいっぱいなので載せました。 どうかよろしくお願いします。

  • VC++6.0 Stack Overflow !!

    私はVC++6.0で画像処理のプログラムを作っています。 再帰構造を用いたある関数を呼ぶと、対象の画像によって、スタックオーバーフローでプログラムが止まってしまいます。 これを避ける方法や、スタックのサイズの変更方法がありましたら教えてください。 なお、現在、VC++のメニューの「プロジェクト」→「設定」→「リンク」タブ→「アウトプット」カテゴリからスタックアロケーションの予約のところを適当な大きな数を入力しているのですが、効果はありません。

  • 関数呼び出しでのスタック消費量

    C++で関数を再帰呼び出しするとスタックオーバーフローとなりました。 それで、どれ位なら可能か調べるため、次の関数で試してみました。 int null(void) {  int a;  return null(); } 結果は次の通りでした。 1回目 &a 0x001cf9d4 2回目 &a 0x001cf8f4 3回目 &a 0x001cf814 よって、一回の呼び出しでスタックをe0(224)バイト使用してるようです。 なぜ(何に)こんなにも多く使うのでしょうか? 環境は、Vista Home Premium、Microsoft Visual C++ 2010 Express です。

  • 【C言語】再帰が時間がかかる理由について

    再帰のプログラムがなぜ時間がかかるのかを詳しく調べています。 アセンブリレベルで見ると、 callに時間がかかるのか?それともpop命令?それとも退避? 結局、よく分からないまま、 Google検索して調べています。(まだよくわかってません。) 再帰呼び出しのデメリットは、 スタック領域を大量に消費する、関数呼び出しのオーバーヘッドであること。 この事実はわかりました。 しかしながら、 オーバーヘッドとは具体的に何なのか これを調べています。 どなたか、良いサイト・書籍を知らないでしょうか。 教えてください。

専門家に質問してみよう