このプログラムは整数ごとに終了しますか?


14

GATE準備のパートテストでは、次の質問がありました。

f(n):
     if n is even: f(n) = n/2
     else f(n) = f(f(n-1))

「すべての整数で終了します」と答えました。なぜなら、いくつかの負の整数であっても、Stack Overflow Errorとして終了するからです。

しかし、私の友人は、これは実装されたコードではなく、単なる擬似コードであるため、いくつかの負の整数の場合は無限再帰になると反対しました。

正しい答えとその理由は?


8
n = -1の場合は終了しません。このような場合、ほとんどの場合理論上の制限が考慮されます。
ディープジョシ

9
スタックオーバーフローを終了と見なす場合、すべてのプログラムが終了し、この質問の目的に
反し

10
@ xuq01 while (true);は終了せず、賢明なことでもスタックオーバーフローを引き起こしません。
TripeHound

3
私はおそらく、「使用しているべきではありません@leftaroundabout 賢明なもので、それは完全に異なるレベルだから」「賢明な ...スポッティングと末尾再帰を実装する」で素敵な(あるいは賢明な)が、そうではないことは少しだけ」である賢明ではありません「。スタックwhile(true);を使用する方法で実装されたものはほとんど間違いなく賢明ではありません。重要なのは、意図的に邪魔にならないように邪魔しない限り、while(true);スタックオーバーフローを終了もトリガーもしないことです。
-TripeHound

14
@ xuq01「宇宙の破壊」は、停止する問題の解決策とは考えていません。
-TripeHound

回答:


49

正解は、この関数はすべての整数で終了しないことです(具体的には、-1で終了しません)。あなたの友人は、これが擬似コードであり、擬似コードがスタックオーバーフローで終了しないと述べているのは正しいです。擬似コードは正式には定義されていませんが、アイデアはブリキに書かれていることを実行するというものです。コードに「スタックオーバーフローエラーで終了」と表示されていない場合、スタックオーバーフローエラーはありません。

これが実際のプログラミング言語であったとしても、スタックの使用が言語の定義の一部でない限り、正しい答えは依然として「終了しない」です。ほとんどの言語は、スタックがオーバーフローする可能性のあるプログラムの動作を指定しません。プログラムが使用するスタックの量を正確に知ることは難しいからです。

実際のインタープリターまたはコンパイラーでコードを実行すると、多くの言語でスタックオーバーフローが発生する場合、それは言語の正式なセマンティクスと実装の間に矛盾があります。一般に、言語の実装は、有限のメモリを備えた具体的なコンピュータで実行できることのみを行うと理解されています。プログラムがスタックオーバーフローで死ぬ場合は、より大きなコンピューターを購入し、必要に応じてシステムを再コンパイルして、すべてのメモリをサポートし、再試行します。プログラムが終了していない場合、永久にこれを実行する必要がある場合があります。

テールコールの最適化やメモ化などの一部の最適化により、定数バインドスタックスペースで関数呼び出しの無限チェーンが可能になるため、プログラムがスタックをオーバーフローするかどうかは明確に定義されていません。一部の言語仕様では、可能な場合に実装でテールコールの最適化を実行することも義務付けられています(これは関数型プログラミング言語では一般的です)。この関数でf(-1)は、に展開しf(f(-2))ます。の外側の呼び出しfは末尾呼び出しなので、スタックに何もプッシュせず、スタックにのみf(-2)移動し、それがを返す-1ため、スタックは最初の状態に戻ります。したがって、末尾呼び出しの最適化f(-1)では、一定のメモリ内で永久にループします。


3
プログラミング言語に変換されたコードがスタックオーバーフローを引き起こさない例は、Haskellです。それはただ無限にループします:let f :: Int -> Int; f n = if even n then n `div` 2 else f (f (n - 1)) in f (-1)
JoL

5

これをC言語の観点から見ると、実装は、元のコードが未定義の動作を呼び出さないすべての場合に、同じ結果を生成するコードにコードを置き換えることができます。だからそれは置き換えることができます

f(n):
   if n is even: f(n) = n/2
   else f(n) = f(f(n-1))

f(n):
   if n is even: f(n) = n/2
   else f(n) = f((n-1) / 2)

これで、実装は末尾再帰を適用できます。

f(n):
   while n is not even do n = (n-1) / 2
   f(n) = n/2

そして、n = -1の場合に限り、これは永久にループします。


Cでは、呼び出しf(-1)は未定義の動作であると思います(実装は、すべてのスレッドが終了するか、この関数が実行しないアクティビティの短いリスト内の何かを行うと想定する場合があります)ので、コンパイラは実際にその中で必要なことを行うことができます場合!
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.