¿Por qué esto entra en un bucle infinito?


493

Tengo el siguiente código:

public class Tests {
    public static void main(String[] args) throws Exception {
        int x = 0;
        while(x<3) {
            x = x++;
            System.out.println(x);
        }
    }
}

Sabemos que debería haber escrito solo x++o x=x+1, pero x = x++primero debería atribuirse xa sí mismo y luego incrementarlo. ¿Por qué xcontinúa con 0como valor?

--actualizar

Aquí está el código de bytes:

public class Tests extends java.lang.Object{
public Tests();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[])   throws java.lang.Exception;
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   iconst_3
   4:   if_icmpge   22
   7:   iload_1
   8:   iinc    1, 1
   11:  istore_1
   12:  getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   15:  iload_1
   16:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   19:  goto    2
   22:  return

}

Leeré sobre las instrucciones para tratar de entender ...


8
Sospecho que lo que está sucediendo es: 1. cargar x en un registro (= 0); 2. incremento x (x = 1); 3. guardar el valor del registro en x (x = 0). En C / C ++, este sería un comportamiento indefinido porque no hay un punto de secuencia formal para definir el orden de 2 y 3. Esperemos que alguien pueda citarle algo equivalente de la especificación de Java.
Rup

19
Intentamos esto en C ++ para ver qué sucedería, e imprime 1,2,3 y sale. No esperaba eso. Supongo que depende del compilador, ya que es un comportamiento indefinido. Utilizamos el gnu g ++.
llorar

13
@saj x++es post-incremento; x=es la asignación del resultado ; el resultado de x++es el original x(y hay un efecto secundario de incremento, pero eso no cambia el resultado), así que esto puede interpretarse comovar tmp = x; x++; x = tmp;
Marc Gravell

55
Ahora que tengo una pregunta popular que lamento, las respuestas (repetidas) no dejan de llegar, incluso después de elegir la respuesta correcta. Mi pantalla de "Actividad reciente" está llena de las mismas respuestas, y viene más ...
Tom Brito

3
@Rob Vermeulen es posible que desee leer la pregunta completa antes de hacer un comentario ...;) Este fue un código hecho por un estudiante mío, y tenía curiosidad por qué de este comportamiento.
Tom Brito

Respuestas:


354

Nota : Originalmente publiqué el código C # en esta respuesta con fines ilustrativos, ya que C # le permite pasar intparámetros por referencia con la refpalabra clave. Decidí actualizarlo con un código Java legal real usando la primera MutableIntclase que encontré en Google para aproximarme a lo que refhace en C #. Realmente no puedo decir si eso ayuda o perjudica la respuesta. Diré que personalmente no he hecho tanto desarrollo de Java; por lo que sé, podría haber formas mucho más idiomáticas de ilustrar este punto.


Quizás si escribimos un método para hacer el equivalente de lo x++que hace, esto lo aclarará más.

public MutableInt postIncrement(MutableInt x) {
    int valueBeforeIncrement = x.intValue();
    x.add(1);
    return new MutableInt(valueBeforeIncrement);
}

¿Derecha? Incremente el valor pasado y devuelva el valor original: esa es la definición del operador postincremento.

Ahora, veamos cómo se desarrolla este comportamiento en su código de ejemplo:

MutableInt x = new MutableInt();
x = postIncrement(x);

postIncrement(x)que hace Incrementos x, sí. Y luego devuelve lo que x estaba antes del incremento . Este valor de retorno se asigna a x.

Entonces, el orden de los valores asignados xes 0, luego 1, luego 0.

Esto podría ser más claro aún si reescribimos lo anterior:

MutableInt x = new MutableInt();    // x is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
x = temp;                           // Now x is 0 again.

Su fijación en el hecho de que cuando reemplaza xen el lado izquierdo de la asignación anterior con y"puede ver que primero incrementa x, y luego lo atribuye a y" me confunde. No es a lo xque se le está asignando y; es el valor anteriormente asignadox . Realmente, la inyección yhace que las cosas no sean diferentes del escenario anterior; simplemente tenemos:

MutableInt x = new MutableInt();    // x is 0.
MutableInt y = new MutableInt();    // y is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
y = temp;                           // y is still 0.

Entonces está claro: x = x++efectivamente no cambia el valor de x. Siempre hace que x tenga los valores x 0 , luego x 0 + 1 y luego x 0 nuevamente.


Actualización : Incidentalmente, para que no dude que xalguna vez se asigna a 1 "entre" la operación de incremento y la asignación en el ejemplo anterior, he preparado una demostración rápida para ilustrar que este valor intermedio realmente "existe", aunque lo hará nunca ser "visto" en el hilo de ejecución.

La demostración llama x = x++;en un bucle mientras un hilo separado imprime continuamente el valor de xen la consola.

public class Main {
    public static volatile int x = 0;

    public static void main(String[] args) {
        LoopingThread t = new LoopingThread();
        System.out.println("Starting background thread...");
        t.start();

        while (true) {
            x = x++;
        }
    }
}

class LoopingThread extends Thread {
    public @Override void run() {
        while (true) {
            System.out.println(Main.x);
        }
    }
}

A continuación se muestra un extracto de la salida del programa anterior. Observe la ocurrencia irregular de 1s y 0s.

Iniciando hilo de fondo ...
0 0
0 0
1
1
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
1
0 0
1

1
No necesita crear una clase para pasar por referencia en Java (aunque eso ciertamente funcionaría). Puede usar la Integerclase, que es parte de la biblioteca estándar, e incluso tiene la ventaja de estar en caja automática desde y hacia int casi de forma transparente.
rmeador

3
@rmeador Integer es inmutable, por lo que aún no puede cambiar su valor. AtomicInteger, sin embargo, es mutable.
ILMTitan

55
@Dan: Por cierto, xen su último ejemplo debe declararse volatile, de lo contrario, es un comportamiento indefinido y ver 1s es específico de la implementación.
axtavt

44
@burkestar: No creo que ese enlace sea bastante apropiado en este caso, ya que es una pregunta de Java y (a menos que me equivoque) el comportamiento en realidad no está definido en C ++.
Dan Tao

55
@Tom Brito: en C no está definido ... se ++ podría hacer antes o después de la asignación. Prácticamente hablando, puede haber un compilador que haga lo mismo que Java, pero no querrás apostar por él.
detly

170

x = x++ funciona de la siguiente manera:

  • Primero evalúa la expresión x++. La evaluación de esta expresión produce un valor de expresión (que es el valor de xantes del incremento) e incrementos x.
  • Posteriormente asigna el valor de la expresión a x, sobrescribiendo el valor incrementado

Entonces, la secuencia de eventos se ve de la siguiente manera (es un bytecode descompilado real, como el producido por javap -c, con mis comentarios):

   8: iload_1 // Recordar el valor actual de x en la pila
   9: iinc 1, 1 // Incremento x (no cambia la pila)
   12: istore_1 // Escribe el valor rememorado de la pila en x

A modo de comparación x = ++x:

   8: iinc 1, 1 // Incremento x
   11: iload_1 // Empuja el valor de x en la pila
   12: istore_1 // Valor emergente de la pila a x

Si realiza una prueba, puede ver que primero se incrementa y luego se atribuye. Por lo tanto, no debe atribuir cero.
Tom Brito

2
@Tom ese es el punto, sin embargo, porque esta es una secuencia única, está haciendo las cosas en un orden no obvio (y probablemente indefinido). Al intentar probar esto, está agregando un punto de secuencia y obteniendo un comportamiento diferente.
Rup

Con respecto a su salida de código de bytes: tenga en cuenta que iincincrementa una variable, no incrementa un valor de pila, ni deja un valor en la pila (a diferencia de casi cualquier otra operación aritmética). Es posible que desee agregar el código generado por ++xpara la comparación.
Anon

3
@Rep Puede que no esté definido en C o C ++, pero en Java está bien definido.
ILMTitan


104

Esto sucede porque el valor de xno se incrementa en absoluto.

x = x++;

es equivalente a

int temp = x;
x++;
x = temp;

Explicación:

Veamos el código de byte para esta operación. Considere una clase de muestra:

class test {
    public static void main(String[] args) {
        int i=0;
        i=i++;
    }
}

Ahora ejecutando el desensamblador de clase en esto obtenemos:

$ javap -c test
Compiled from "test.java"
class test extends java.lang.Object{
test();
  Code:
   0:    aload_0
   1:    invokespecial    #1; //Method java/lang/Object."<init>":()V
   4:    return

public static void main(java.lang.String[]);
  Code:
   0:    iconst_0
   1:    istore_1
   2:    iload_1
   3:    iinc    1, 1
   6:    istore_1
   7:    return
}

Ahora, la máquina virtual Java está basada en la pila, lo que significa que para cada operación, los datos se enviarán a la pila y desde la pila, los datos saldrán para realizar la operación. También hay otra estructura de datos, típicamente una matriz para almacenar las variables locales. Las variables locales reciben identificadores que son solo los índices de la matriz.

Veamos la mnemotecnia en el main()método:

  • iconst_0: El valor constante 0 se transfiere a la pila.
  • istore_1: El elemento superior de la pila aparece y se almacena en la variable local con el índice 1
    que es x.
  • iload_1: El valor en la ubicación 1cuyo valor x es 0, se inserta en la pila.
  • iinc 1, 1: El valor en la ubicación de la memoria 1se incrementa en 1. Entonces xahora se convierte 1.
  • istore_1: El valor en la parte superior de la pila se almacena en la ubicación de la memoria 1. Eso se 0asigna a x sobrescribir su valor incrementado.

Por lo tanto, el valor de xno cambia dando como resultado el bucle infinito.


55
En realidad, se incrementa (ese es el significado de ++), pero la variable se sobrescribe más tarde.
Progman

10
int temp = x; x = x + 1; x = temp;es mejor no usar una tautología en su ejemplo.
Scott Chamberlain

52
  1. La notación de prefijo incrementará la variable ANTES de evaluar la expresión.
  2. La notación de postfix aumentará DESPUÉS de la evaluación de la expresión.

Sin embargo, " =" tiene una prioridad de operador menor que "++ ".

Entonces x=x++;debe evaluar de la siguiente manera

  1. x preparado para la asignación (evaluado)
  2. x incrementado
  3. Valor anterior de xasignado a x.

Esta es la mejor respuesta. Un margen de beneficio lo habría ayudado a destacar un poco más.
Justin Force

1
Esto está mal. No se trata de precedencia. ++tiene mayor prioridad que =en C y C ++, pero la declaración no está definida en esos lenguajes.
Matthew Flaschen

2
La pregunta original es sobre Java
Jaydee

34

Ninguna de las respuestas fue acertada, así que aquí va:

Cuando estás escribiendo int x = x++, no estás asignando xser él mismo en el nuevo valor, estás asignando xser el valor de retorno de la x++expresión. Que resulta ser el valor original de x, como se insinúa en la respuesta de Colin Cochrane .

Por diversión, pruebe el siguiente código:

public class Autoincrement {
        public static void main(String[] args) {
                int x = 0;
                System.out.println(x++);
                System.out.println(x);
        }
}

El resultado será

0
1

El valor de retorno de la expresión es el valor inicial de x, que es cero. Pero más tarde, cuando leemos el valor de x, recibimos el valor actualizado, ese es uno.


Trataré de entender las líneas de bytecode, veré mi actualización, para que quede claro ... :)
Tom Brito

Usar println () fue muy útil para entender esto.
ErikE

29

Ya ha sido explicado bien por otros. Solo incluyo los enlaces a las secciones de especificación de Java relevantes.

x = x ++ es una expresión. Java seguirá el orden de evaluación . Primero evaluará la expresión x ++, que incrementará x y establecerá el valor del resultado al valor anterior de x . Luego asignará el resultado de la expresión a la variable x. Al final, x vuelve a su valor anterior.


1
+1. Esta es, con mucho, la mejor respuesta a la pregunta real, "¿Por qué?"
Matthew Flaschen

18

Esta declaración:

x = x++;

evalúa así:

  1. Empuje xsobre la pila;
  2. Incremento x;
  3. Pop xde la pila.

Entonces el valor no ha cambiado. Compare eso con:

x = ++x;

que se evalúa como:

  1. Incremento x;
  2. Empuje xsobre la pila;
  3. Pop xde la pila.

Lo que quieres es:

while (x < 3) {
  x++;
  System.out.println(x);
}

13
Definitivamente la implementación correcta, pero la pregunta es "¿por qué?".
p.campbell

1
El código original usaba post-incremento en x y luego lo asignaba a x. x se unirá a x antes del incremento, por lo tanto, nunca cambiará los valores.
wkl

55
@cletus No soy el votante, pero su respuesta inicial no contenía la explicación. Simplemente dijo 'x ++'.
Petar Minchev

44
@cletus: no voté en contra, pero su respuesta originalmente fue solo el x++fragmento de código.
p.campbell

10
La explicación también es incorrecta. Si el código asignó primero x a x y luego incrementó x, funcionaría bien. Simplemente cambie x++;su solución x=x; x++;y está haciendo lo que dice que está haciendo el código original.
Wooble

10

La respuesta es bastante directa. Tiene que ver con el orden en que se evalúan las cosas. x++devuelve el valor y xluego incrementax .

En consecuencia, el valor de la expresión x++es 0. Entonces estás asignando x=0cada vez en el ciclo. Ciertamente x++incrementa este valor, pero eso sucede antes de la asignación.


1
Wow, hay tantos detalles en esta página cuando la respuesta es corta y simple, es decir, esta.
Charles Goodwin

8

De http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html

Los operadores de incremento / decremento pueden aplicarse antes (prefijo) o después (postfijo) del operando. El resultado del código ++; y ++ resultado; ambos terminarán en un resultado que se incrementará en uno. La única diferencia es que la versión del prefijo (resultado ++) se evalúa al valor incrementado, mientras que la versión postfix (resultado ++) se evalúa al valor original . Si solo está realizando un incremento / decremento simple, realmente no importa qué versión elija. Pero si usa este operador en parte de una expresión más grande, la que elija puede hacer una diferencia significativa.

Para ilustrar, intente lo siguiente:

    int x = 0;
    int y = 0;
    y = x++;
    System.out.println(x);
    System.out.println(y);

Que imprimirá 1 y 0.


1
Sin embargo, el problema no es el resultado de la evaluación, sino el orden de las tiendas.
Rup

2
Estoy en desacuerdo. Si x = 0, entonces x ++ devolverá 0. Por lo tanto, x = x ++ dará como resultado x = 0.
Colin Cochrane

Rup tiene razón en esto. Es el orden de las tiendas lo que está en juego en este caso particular. y = x ++ no es lo mismo que x = x ++; En el último, a x se le asignan 2 valores en la misma expresión. A la izquierda x se le está asignando el resultado de la evaluación de la expresión x ++, que es 0. El lado derecho x se está incrementando a 1. En este orden se trata el problema. De publicaciones anteriores, está claro que la forma en que esto funciona es: eval = x ++ => eval == 0: incremento a la derecha x => x == 1: izquierda x = eval => x == 0
Michael Ekoka

7

Efectivamente está obteniendo el siguiente comportamiento.

  1. agarre el valor de x (que es 0) como "el resultado" del lado derecho
  2. incrementar el valor de x (entonces x es ahora 1)
  3. asignar el resultado del lado derecho (que se guardó como 0) a x (x ahora es 0)

La idea es que el operador posterior al incremento (x ++) incremente esa variable en cuestión DESPUÉS de devolver su valor para usar en la ecuación en la que se usa.

Editar: agregando un poco debido al comentario. Considérelo como lo siguiente.

x = 1;        // x == 1
x = x++ * 5;
              // First, the right hand side of the equation is evaluated.
  ==>  x = 1 * 5;    
              // x == 2 at this point, as it "gave" the equation its value of 1
              // and then gets incremented by 1 to 2.
  ==>  x = 5;
              // And then that RightHandSide value is assigned to 
              // the LeftHandSide variable, leaving x with the value of 5.

OK, pero ¿qué especifica el orden de los pasos 2 y 3?
Rup

@Rup: el lenguaje lo define. Primero se evalúa el lado derecho de la ecuación (en este caso, "x ++"), y el resultado se asigna a la variable del lado izquierdo. Así funciona el lenguaje. En cuanto a la "x ++" "que devuelve" x para la ecuación, así es como funciona el operador de incremento de postfix (devuelve el valor de x, luego increméntelo). Si hubiera sido "--x", lo habría sido (incremente x, luego devuelva el valor). Retorno no es la palabra correcta allí, pero se entiende la idea.
RHSeeger

7

Realmente no necesita el código de la máquina para comprender lo que está sucediendo.

Según las definiciones:

  1. El operador de asignación evalúa la expresión del lado derecho y la almacena en una variable temporal.

    1.1. El valor actual de x se copia en esta variable temporal

    1.2. x se incrementa ahora.

  2. La variable temporal se copia en el lado izquierdo de la expresión, que es x por casualidad. Por eso, el antiguo valor de x se copia nuevamente en sí mismo.

Es bastante simple


5

Esto se debe a que nunca se incrementa en este caso. x++usará su valor primero antes de incrementar como en este caso será como:

x = 0;

Pero si haces ++x;esto aumentará.


Si realiza una prueba, puede ver que primero se incrementa y luego se atribuye. Por lo tanto, no debe atribuir cero.
Tom Brito

1
@Tom: vea mi respuesta: muestro en una prueba que x ++ realmente devuelve el valor anterior de x Ahí es donde se rompe.
Robert Munteanu

"si haces una prueba": algunas personas parecen pensar que una prueba escrita en C nos dice qué hará Java, cuando ni siquiera nos dirá qué hará C.
Jim Balter

3

El valor permanece en 0 porque el valor de x++es 0. En este caso, no importa si el valor de xse incrementa o no, la asignación x=0se ejecuta. Esto sobrescribirá el valor temporal incrementado de x(que fue 1 por "muy poco tiempo").


Pero x ++ es una operación posterior. Por lo tanto, x tendría que incrementarse después de completar la asignación.
Sagar V

1
@Sagar V: solo para la expresión x++, no para toda la tareax=x++;
Progman

No, creo que solo necesita incrementarse después de leer el valor de x que se utilizará en la asignación.
Rup

1

Esto funciona como espera que el otro lo haga. Es la diferencia entre prefijo y postfix.

int x = 0; 
while (x < 3)    x = (++x);

1

Piense en x ++ como una llamada de función que "devuelve" lo que X era antes del incremento (es por eso que se llama un incremento posterior).

Por lo tanto, el orden de operación es:
1: almacena en caché el valor de x antes de incrementar
2: incrementa x
3: devuelve el valor almacenado en caché (x antes de que se incremente)
4: el valor de retorno se asigna a x


OK, pero ¿qué especifica el orden de los pasos 3 y 4?
Rup

"devuelve lo que era X antes del incremento" está mal, vea mi actualización
Tom Brito

En realidad, los pasos 3 y 4 no son operaciones separadas: no es realmente una llamada a una función que devuelve un valor, solo ayuda a pensarlo de esa manera. Cada vez que tiene una tarea, el lado derecho es "evaluado", entonces el resultado se asigna al lado izquierdo, el resultado de la evaluación puede considerarse como un valor de retorno, ya que le ayuda a comprender el orden de las operaciones, pero en realidad no es .
jhabbott

Vaya, cierto. Me refería a los pasos 2 y 4: ¿por qué el valor devuelto se almacena por encima del valor incrementado?
Rup

1
Esto es parte de la definición de una operación de asignación, primero se evalúa completamente el lado derecho, luego el resultado se asigna al lado izquierdo.
jhabbott

1

Cuando ++ está en rhs, el resultado se devuelve antes de que se incremente el número. Cambie a ++ x y hubiera estado bien. Java habría optimizado esto para realizar una sola operación (la asignación de x a x) en lugar del incremento.


1

Bueno, hasta donde puedo ver, el error ocurre, debido a que la asignación anula el valor incrementado, con el valor anterior al incremento, es decir, deshace el incremento.

Específicamente, la expresión "x ++" tiene el valor de 'x' antes del incremento en oposición a "++ x" que tiene el valor de 'x' después del incremento.

Si está interesado en investigar el código de bytes, echaremos un vistazo a las tres líneas en cuestión:

 7:   iload_1
 8:   iinc    1, 1
11:  istore_1

7: iload_1 # Pondrá el valor de la 2da variable local en la pila
8: iinc 1,1 # incrementará la 2da variable local con 1, ¡tenga en cuenta que deja la pila intacta!
9: istore_1 # Aparecerá la parte superior de la pila y guardará el valor de este elemento en la segunda variable local
(Puede leer los efectos de cada instrucción JVM aquí )

Esta es la razón por la cual el código anterior se repetirá indefinidamente, mientras que la versión con ++ x no lo hará. El bytecode para ++ x debería ser bastante diferente, por lo que recuerdo del compilador 1.3 Java que escribí hace poco más de un año, el bytecode debería ser algo así:

iinc 1,1
iload_1
istore_1

Entonces, simplemente intercambiando las dos primeras líneas, cambia la semántica para que el valor que queda en la parte superior de la pila, después del incremento (es decir, el "valor" de la expresión) sea el valor después del incremento.


1
    x++
=: (x = x + 1) - 1

Entonces:

   x = x++;
=> x = ((x = x + 1) - 1)
=> x = ((x + 1) - 1)
=> x = x; // Doesn't modify x!

Mientras

   ++x
=: x = x + 1

Entonces:

   x = ++x;
=> x = (x = x + 1)
=> x = x + 1; // Increments x

Por supuesto, el resultado final es el mismo que solo x++;o ++x;en una línea por sí mismo.


0
 x = x++; (increment is overriden by = )

debido a la declaración anterior x nunca llega a 3;


0

Me pregunto si hay algo en la especificación de Java que defina con precisión el comportamiento de esto. (La implicación obvia de esa afirmación es que soy demasiado vago para verificarlo).

Nota del código de bytes de Tom, las líneas clave son 7, 8 y 11. La línea 7 carga x en la pila de cálculo. La línea 8 incrementa x. La línea 11 almacena el valor de la pila de nuevo a x. En los casos normales en los que no se asignan valores a sí mismos, no creo que haya alguna razón por la que no pueda cargar, almacenar e incrementar. Obtendría el mismo resultado.

Como, supongamos que tiene un caso más normal en el que escribió algo como: z = (x ++) + (y ++);

Si dijo (pseudocódigo para omitir tecnicismos)

load x
increment x
add y
increment y
store x+y to z

o

load x
add y
store x+y to z
increment x
increment y

debería ser irrelevante Cualquiera de las dos implementaciones debería ser válida, creo.

Sería extremadamente cauteloso al escribir código que depende de este comportamiento. Me parece muy dependiente de la implementación, entre las grietas en las especificaciones. La única vez que marcaría la diferencia es si hiciste algo loco, como en el ejemplo aquí, o si tienes dos hilos ejecutándose y dependías del orden de evaluación dentro de la expresión.



0

La x++expresión se evalúa como x. La ++parte afecta el valor después de la evaluación , no después de la declaración . por lo que x = x++se traduce efectivamente en

int y = x; // evaluation
x = x + 1; // increment part
x = y; // assignment

0

Antes de incrementar el valor en uno, el valor se asigna a la variable.


0

Está sucediendo porque se incrementó después. Significa que la variable se incrementa después de evaluar la expresión.

int x = 9;
int y = x++;

x es ahora 10, pero y es 9, el valor de x antes de que se incrementara.

Ver más en Definición de incremento posterior .


1
Su x/ yejemplo es diferente del código real, y la diferencia es relevante. Su enlace ni siquiera menciona Java. Para dos de los idiomas que no menciona, la declaración en la pregunta no está definido.
Matthew Flaschen

0

Comprueba el siguiente código,

    int x=0;
    int temp=x++;
    System.out.println("temp = "+temp);
    x = temp;
    System.out.println("x = "+x);

la salida será

temp = 0
x = 0

post incrementsignifica incrementar el valor y devolver el valor antes del incremento . Es por ello que el valor tempes 0. Entonces, ¿qué temp = ipasa si esto está en un bucle (excepto la primera línea de código)? justo como en la pregunta !!!!


-1

El operador de incremento se aplica a la misma variable a la que está asignando. Eso es pedir problemas. Estoy seguro de que puede ver el valor de su variable x mientras ejecuta este programa ... eso debería dejar en claro por qué el ciclo nunca termina.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.