In einem Teiltest für die GATE-Vorbereitung gab es eine Frage:
f(n):
if n is even: f(n) = n/2
else f(n) = f(f(n-1))
Ich antwortete "Es wird für alle Ganzzahlen beendet", da es selbst für einige negative Ganzzahlen als Stapelüberlauffehler beendet wird .
Aber mein Freund widersprach der Aussage, dass es sich bei einigen negativen ganzen Zahlen um eine unendliche Rekursion handelt, da es sich nicht um implementierten Code und nur um Pseudocode handelt.
Welche Antwort ist richtig und warum?
algorithms
recursion
pseudocode
Prakhar Londhe
quelle
quelle
while (true);
wird nicht beendet und verursacht auf keinen vernünftigen Fall einen Stapelüberlauf.while(true);
, dass ein Stack verwendet wird, ist definitiv nicht sinnvoll . Der Punkt ist, es sei denn, Sie haben sich absichtlich bemüht, umständlich zu sein,while(true);
weder zu beenden noch einen Stapelüberlauf auszulösen.Antworten:
Die richtige Antwort lautet, dass diese Funktion nicht für alle Ganzzahlen beendet wird (insbesondere endet sie nicht bei -1). Ihr Freund hat Recht, wenn er angibt, dass dies Pseudocode ist und Pseudocode nicht bei einem Stapelüberlauf endet. Pseudocode ist formal nicht definiert, aber die Idee ist, dass es das tut, was auf dem Zinn steht. Wenn der Code nicht "Beenden mit einem Stapelüberlauffehler" sagt, liegt kein Stapelüberlauffehler vor.
Selbst wenn dies eine echte Programmiersprache wäre, wäre die richtige Antwort immer noch "nicht terminieren", es sei denn, die Verwendung eines Stacks ist Teil der Definition der Sprache. Die meisten Sprachen spezifizieren nicht das Verhalten von Programmen, die den Stapel überlaufen könnten, da es schwierig ist, genau zu wissen, wie viel Stapel ein Programm verwenden wird.
Wenn das Ausführen des Codes auf einem tatsächlichen Interpreter oder Compiler in vielen Sprachen einen Stapelüberlauf verursacht, ist dies eine Diskrepanz zwischen der formalen Semantik der Sprache und der Implementierung. Es ist allgemein bekannt, dass Implementierungen einer Sprache nur das tun, was auf einem konkreten Computer mit endlichem Speicher möglich ist. Wenn das Programm mit einem Stapelüberlauf abstürzt, sollten Sie einen größeren Computer kaufen, das System bei Bedarf neu kompilieren, um den gesamten Speicher zu unterstützen, und es erneut versuchen. Wenn das Programm nicht beendet wird, müssen Sie dies möglicherweise für immer tun.
Sogar die Tatsache, dass ein Programm den Stapel überläuft oder nicht überläuft, ist nicht genau definiert, da einige Optimierungen, wie die Optimierung von Tail Calls und das Merken , eine unendliche Kette von Funktionsaufrufen in einem konstant gebundenen Stapelraum ermöglichen können. Einige Sprachspezifikationen verlangen sogar, dass Implementierungen, wenn möglich, eine Tail-Call-Optimierung durchführen (dies ist in funktionalen Programmiersprachen üblich). Erweitert für diese Funktion
f(-1)
auff(f(-2))
; Der äußere Aufruf vonf
ist ein Tail-Aufruf, sodass nichts auf den Stapel geschoben wird, sondern nurf(-2)
auf den Stapel verschoben-1
wird. Das Ergebnis ist, dass der Stapel wieder in dem Zustand ist, in dem er zu Beginn war. Somit bleibt bei der Tail-Call-Optimierung dief(-1)
Schleife für immer im Dauerspeicher.quelle
let f :: Int -> Int; f n = if even n then n `div` 2 else f (f (n - 1)) in f (-1)
Wenn wir dies in Bezug auf die C-Sprache betrachten, kann eine Implementierung Code durch Code ersetzen, der in allen Fällen, in denen das Original kein undefiniertes Verhalten hervorruft, dasselbe Ergebnis liefert. So kann es ersetzen
mit
Jetzt darf die Implementierung die Schwanzrekursion anwenden:
Und dies ist genau dann eine Endlosschleife, wenn n = -1 ist.
quelle
f(-1)
undefiniertes Verhalten (die Implementierung kann davon ausgehen, dass jeder Thread in einer kurzen Liste von Aktivitäten, die diese Funktion nicht ausführt, entweder beendet oder etwas anderes ausführt), sodass der Compiler tatsächlich alles tun kann, was er will Fall!