¿Terminará este programa por cada entero?

14

En una prueba parcial para la preparación de GATE había una pregunta:

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

Respondí "Terminará para todos los enteros", porque incluso para algunos enteros negativos, terminará como Error de desbordamiento de pila .

Pero mi amigo no estuvo de acuerdo en decir que, dado que este no es un código implementado y solo un pseudocódigo, será una recursión infinita en el caso de algunos enteros negativos.

¿Qué respuesta es correcta y por qué?

Prakhar Londres
fuente
8
No termina para n = -1. La mayoría de los límites teóricos se consideran en tales casos.
Deep Joshi
99
Si desbordamiento de pila es para ser considerada como la terminación, a continuación, todos los programas y terminarán en contra del propósito de esta pregunta ...
xuq01
10
@ xuq01 while (true);no terminará ni, en nada sensato, provocará el desbordamiento de la pila.
TripeHound
3
@leftaroundabout Probablemente no debería haber usado " en nada sensato " porque es un nivel completamente diferente de " sensato " ... detectar e implementar la recursión de la cola es bueno (o incluso sensato ), pero no hacerlo es un poco " poco sensato" ". Cualquier cosa que se implemente while(true);de una manera que use cualquier pila definitivamente no sería sensata . El punto es que, a menos que deliberadamente haya hecho todo lo posible para ser incómodo, while(true);no terminará ni activará el desbordamiento de la pila.
TripeHound
14
@ xuq01 No creo que la "destrucción del universo" cuente como una solución al problema de detención.
TripeHound

Respuestas:

49

La respuesta correcta es que esta función no termina para todos los enteros (específicamente, no termina en -1). Su amigo tiene razón al afirmar que se trata de pseudocódigo y que el pseudocódigo no termina en un desbordamiento de pila. El pseudocódigo no está formalmente definido, pero la idea es que haga lo que dice la lata. Si el código no dice "terminar con un error de desbordamiento de pila", entonces no hay error de desbordamiento de pila.

Incluso si este fuera un lenguaje de programación real, la respuesta correcta sería "no termina", a menos que el uso de una pila sea parte de la definición del lenguaje. La mayoría de los lenguajes no especifican el comportamiento de los programas que pueden desbordar la pila, porque es difícil saber con precisión cuánta pila usará un programa.

Si ejecutar el código en un intérprete o compilador real provoca un desbordamiento de la pila, en muchos idiomas, es una discrepancia entre la semántica formal del lenguaje y la implementación. En general, se entiende que las implementaciones de un lenguaje solo harán lo que se puede hacer en una computadora concreta con memoria finita. Si el programa muere con un desbordamiento de pila, se supone que debe comprar una computadora más grande, recompilar el sistema si es necesario para admitir toda esa memoria e intentarlo nuevamente. Si el programa no finaliza, es posible que deba seguir haciendo esto para siempre.

Incluso el hecho de que un programa desborde o no la pila no está bien definido, ya que algunas optimizaciones, como la optimización y la memorización de llamadas de cola, pueden permitir una cadena infinita de llamadas a funciones en un espacio de pila con límite constante. Algunas especificaciones de lenguaje incluso exigen que las implementaciones realicen la optimización de llamadas de cola cuando sea posible (esto es común en lenguajes de programación funcionales). Para esta función, se expande a ; la llamada externa a es una llamada de cola, por lo que no empuja nada en la pila, por lo tanto solo va a la pila, y eso regresa , por lo que la pila vuelve al mismo estado en el que estaba al principio. Por lo tanto, con la optimización de llamadas de cola se repite para siempre en la memoria constante.f(-1)f(f(-2))ff(-2)-1f(-1)

Gilles 'SO- deja de ser malvado'
fuente
3
Un ejemplo en el que el código traducido a un lenguaje de programación no produce desbordamiento de pila es Haskell. Simplemente se let f :: Int -> Int; f n = if even n then n `div` 2 else f (f (n - 1)) in f (-1)
repite
5

Si miramos esto en términos del lenguaje C, una implementación es libre de reemplazar código con código que produce el mismo resultado en todos los casos en que el original no invoca un comportamiento indefinido. Entonces puede reemplazar

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

con

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

Ahora la implementación puede aplicar la recursividad de cola:

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

Y esto se repite para siempre si y solo si n = -1.

gnasher729
fuente
Creo, en C, que invocar f(-1)es un comportamiento indefinido (la implementación puede suponer que cada hilo termina o hace algo más en una breve lista de actividades que esta función no realiza), por lo que el compilador puede hacer lo que quiera ¡caso!