problemas problem halting java halting-problem

problem - Bucles infinitos en Java



halting problem wiki (15)

Debido a que analizar el estado de la variable es difícil, el compilador simplemente se dio por vencido y le dejó hacer lo que desea. Además, la especificación de lenguaje de Java tiene reglas claras sobre cómo el compilador puede detectar código inalcanzable .

Hay muchas formas de engañar al compilador, otro ejemplo común es

public void test() { return; System.out.println("Hello"); }

lo cual no funcionaría, ya que el compilador se daría cuenta de que el área era incobrable. En cambio, podrías hacer

public void test() { if (2 > 1) return; System.out.println("Hello"); }

Esto funcionaría dado que el compilador no puede darse cuenta de que la expresión nunca será falsa.

Mira el siguiente ciclo while infinito en Java. Causa un error en tiempo de compilación para la declaración debajo de él.

while(true) { System.out.println("inside while"); } System.out.println("while terminated"); //Unreachable statement - compiler-error.

El siguiente ciclo while while infinito, sin embargo funciona bien y no emite ningún error en el que acabo de reemplazar la condición con una variable booleana.

boolean b=true; while(b) { System.out.println("inside while"); } System.out.println("while terminated"); //No error here.

En el segundo caso también, la declaración después del bucle es obviamente inalcanzable porque la variable booleana b es verdadera, pero el compilador no se queja en absoluto. ¿Por qué?

Editar: La siguiente versión de while queda atrapada en un bucle infinito como obvio, pero no emite errores de compilación para la declaración que aparece debajo, aunque la condición if del bucle siempre es false y, en consecuencia, el bucle nunca puede retornar y puede ser determinado por compilador en el tiempo de compilación en sí.

while(true) { if(false) { break; } System.out.println("inside while"); } System.out.println("while terminated"); //No error here.

while(true) { if(false) { //if true then also return; //Replacing return with break fixes the following error. } System.out.println("inside while"); } System.out.println("while terminated"); //Compiler-error - unreachable statement.

while(true) { if(true) { System.out.println("inside if"); return; } System.out.println("inside while"); //No error here. } System.out.println("while terminated"); //Compiler-error - unreachable statement.

Editar: Lo mismo con if y while .

if(false) { System.out.println("inside if"); //No error here. }

while(false) { System.out.println("inside while"); // Compiler''s complain - unreachable statement. }

while(true) { if(true) { System.out.println("inside if"); break; } System.out.println("inside while"); //No error here. }

La siguiente versión de while también se bloquea en un ciclo infinito.

while(true) { try { System.out.println("inside while"); return; //Replacing return with break makes no difference here. } finally { continue; } }

Esto se debe a que el bloque finally siempre se ejecuta aunque la instrucción return encuentre antes en el bloque try .


Desde la perspectiva del compilador, el b en while(b) podría cambiar a falso en alguna parte. El compilador simplemente no se molesta en verificar.

Por diversión, pruebe while(1 < 2) , for(int i = 0; i < 1; i--) etc.


El compilador no es lo suficientemente sofisticado como para ejecutar los valores que b puede contener (aunque solo lo asigne una vez). El primer ejemplo es fácil para el compilador de ver que será un bucle infinito porque la condición no es variable.


El compilador puede demostrar fácil e inequívocamente que la primera expresión siempre da como resultado un ciclo infinito, pero no es tan fácil para el segundo. En tu ejemplo de juguete es simple, pero ¿y si:

  • los contenidos de la variable se leyeron de un archivo?
  • la variable no era local y podría ser modificada por otro hilo?
  • la variable depende de la entrada de algún usuario?

El compilador claramente no está buscando su caso más simple porque está renunciando a ese camino por completo. ¿Por qué? Porque es mucho más difícil prohibido por la especificación. Ver la sección 14.21 :

(Por cierto, mi compilador se queja cuando la variable se declara final ).


El primer enunciado siempre da como resultado un bucle infinito porque hemos especificado una constante en condición del ciclo while, donde, como en el caso del segundo compilador, supongamos que existe la posibilidad de un cambio de valor de b dentro del ciclo.


En realidad, no creo que nadie lo haya acertado (al menos no en el sentido del cuestionario original). El OQ sigue mencionando:

Correcto, pero irrelevante, ya que b NO se está cambiando en el ciclo

Pero no importa porque la última línea ES alcanzable. Si tomó ese código, lo compiló en un archivo de clase y entregó el archivo de clase a otra persona (por ejemplo, como una biblioteca), podría vincular la clase compilada con código que modifica "b" mediante reflexión, salir del ciclo y causar la última línea para ejecutar.

Esto es cierto para cualquier variable que no sea una constante (o final que compila a una constante en la ubicación donde se usa; algunas veces provoca errores extraños si recompila la clase con la final y no una clase que la haga referencia, la referencia la clase todavía mantendrá el valor anterior sin ningún tipo de error)

Utilicé la capacidad de reflexión para modificar variables privadas no finales de otra clase para aplicar parche de mono a una clase en una biblioteca comprada, corrigiendo un error para poder seguir desarrollando mientras esperábamos parches oficiales del proveedor.

Por cierto, puede que esto no funcione en la actualidad, aunque ya lo he hecho antes, existe la posibilidad de que un bucle tan pequeño se guarde en caché en la memoria caché de la CPU y, dado que la variable no está marcada como volátil, el código almacenado nunca recoger el nuevo valor. Nunca he visto esto en acción, pero creo que es teóricamente cierto.


Es simplemente porque el compilador no hace demasiado trabajo de niñera, aunque es posible.

El ejemplo que se muestra es simple y razonable para que el compilador detecte el ciclo infinito. Pero, ¿qué tal si insertamos 1000 líneas de código sin ninguna relación con la variable b ? Y qué tal esas declaraciones son todas b = true; ? El compilador definitivamente puede evaluar el resultado y decirle que es cierto eventualmente en el ciclo while, pero ¿qué tan lento será compilar un proyecto real?

PD: la herramienta para pelusas definitivamente debería hacerlo por ti.


Este último no es inalcanzable. El booleano b todavía tiene la posibilidad de ser alterado a falso en algún lugar dentro del ciclo causando una condición final.


Las expresiones se evalúan en tiempo de ejecución, por lo que, al reemplazar el valor escalar "verdadero" por algo así como una variable booleana, se cambia un valor escalar a una expresión booleana y, por lo tanto, el compilador no tiene forma de saberlo en tiempo de compilación.


Me sorprende que su compilador se haya negado a compilar el primer caso. Eso me parece extraño.

Pero el segundo caso no está optimizado para el primer caso porque (a) otro hilo podría actualizar el valor de b (b) la función llamada podría modificar el valor de b como un efecto secundario.


Porque true es constante yb se puede cambiar en el ciclo.


Según las especificaciones , se dice lo siguiente sobre declaraciones while.

Una instrucción while se puede completar normalmente si al menos uno de los siguientes es verdadero:

  • La instrucción while es alcanzable y la expresión de condición no es una expresión constante con valor verdadero.
  • Hay una declaración de interrupción alcanzable que sale de la instrucción while.

Entonces, el compilador solo dirá que el código que sigue a una declaración while es inalcanzable si la condición while es una constante con un valor verdadero, o si hay una declaración break dentro del tiempo. En el segundo caso, dado que el valor de b no es una constante, no considera que el código que lo sigue sea inalcanzable. Hay mucha más información detrás de ese enlace para darle más detalles sobre qué es y qué no se considera inalcanzable.


Si el compilador puede determinar de manera concluyente que el valor booleano se evaluará como true en tiempo de ejecución, arrojará ese error. El compilador asume que la variable que ha declarado puede cambiarse (aunque sabemos que aquí los humanos no lo harán).

Para enfatizar este hecho, si las variables se declaran como final en Java, la mayoría de los compiladores arrojarán el mismo error que si sustituyeran el valor. Esto se debe a que la variable se define en tiempo de compilación (y no se puede cambiar en tiempo de ejecución) y, por lo tanto, el compilador puede determinar de manera concluyente que la expresión se evalúa como true en tiempo de ejecución.


Supongo que la variable "b" tiene la posibilidad de cambiar su valor, por lo que el compilador piensa en System.out.println("while terminated"); puede ser alcanzado.


Los compiladores no son perfectos, ni deberían ser

La responsabilidad del compilador es confirmar la sintaxis, no confirmar la ejecución. Los compiladores pueden atrapar y evitar muchos problemas de tiempo de ejecución en un lenguaje fuertemente tipado, pero no pueden detectar todos esos errores.

La solución práctica es tener baterías de pruebas unitarias para complementar las comprobaciones de los compiladores O usar componentes orientados a objetos para implementar lógica que se sabe que son robustas, en lugar de depender de variables primitivas y condiciones de detención.

Strong Typing and OO: aumentar la eficacia del compilador

Algunos errores son de naturaleza sintáctica, y en Java, la tipificación fuerte hace que muchas excepciones de tiempo de ejecución sean detectables. Pero, al usar mejores tipos, puede ayudar a su compilador a aplicar una mejor lógica.

Si desea que el compilador aplique la lógica de manera más efectiva, en Java, la solución es construir objetos robustos y necesarios que puedan hacer cumplir dicha lógica, y usar esos objetos para construir su aplicación, en lugar de primitivos.

Un ejemplo clásico de esto es el uso del patrón de iterador, combinado con el bucle foreach de Java, este constructo es menos vulnerable al tipo de error que ilustra que un bucle while simplista.