2009-04-17 11 views
7

次のようにすると、大きな 'n'のスタックオーバーフローが発生し、その理由を理解できます。このコードによってスタックオーバーフローが発生するのはなぜですか?

def factorial(n) 
    (n > 1) ? (return (n * factorial(n - 1))) : (return 1) 
end 

なぜ次の原因でオーバーフローするのでしょうか?

def factorial(n, k) 
    (n > 1) ? (return factorial(n - 1, lambda {|v| return k.call(v * n)})) : (return k.call(1)) 
end 
+0

オーバーフロー?またはStackOverflow ?! –

+1

-1、ユーザーボイスに属しています。 –

答えて

9

2つ目のアルゴリズムは、それぞれが前のものへの参照を含むラムダプロシージャのn長鎖チェーンを作成します。私はRubyが何をしているのかは正確にはわかりませんが、ラムダ内のk.callも末尾にあるので、スタックが2番目のアルゴリズムでオーバーフローすることはありません。ブライアンの実験が示唆しているように、Rubyが適切なテールコールを持っていない場合、Rubyが尾を変換するのに十分スマートであるにもかかわらず、チェーンの先頭が呼び出されたときにラムダへのネストされた呼び出しの長いチェーンがオーバーフローします-recursive factorialループを呼び出す(=テールコール最適化)。

+0

2番目のバージョンは階乗でまだ再帰しています。私はRubyがTCOをしなかったと思った。しかし、それはラムダに達するまでスタックを吹き飛ばすことはありません。よくわかりません。 (他の誰もがもっと混乱している、あるいは質問を読んでいない。私は適切に他の人たちに下降している) –

+0

"ラムダに達するまでスタックを吹き飛ばすわけではない" - ね?わかりません。 –

+1

2番目の例がまだ 'factorial'で再帰していることを考えると、' k.call(1) 'の基本ケースに達する前にスタックをオーバーフローさせることを期待しました。しかし、上記の2番目の例のコードでは、 'factorial'への呼び出しは、(非常に大きなnの場合でも)スタックをあふれさせません。これは最初の例とは異なる動作です。 2番目の例は、その基本ケースに達していて、たくさんの '' k.call'が発生するまでオーバーフローしません。 TCOがなければ、どのようにそれがそれを実現しているのかわかりません。 –

0

第1の関数と同様に、再帰呼び出しはシステムが処理するには多すぎることがあります。

3

ほとんどの言語では、関数呼び出しはというコールスタックに移動します。これは実際にはスタックにすぎません。関数を再帰的に呼び出すと、呼び出しスタックに追加が保持されます。最終的にスタックを埋めると、スタックのオーバーフローが発生します。あなたの再帰レベルが深刻になる再帰関数を実行すると、それは常に危険です。

+0

-1:cf. "次は大規模なnのためにオーバーフローし、私は理由を理解することができます" –

2

同じ理由で、最初のスタックオーバーフローが発生します。コールスタックが大きくなりすぎます。

関連する問題