Une boucle apparemment sans fin se termine, sauf si System.out.println est utilisé


91

J'avais un simple morceau de code qui était censé être une boucle sans fin car il xaugmentera toujours et restera toujours plus grand que j.

int x = 5;
int y = 9;
for (int j = 0; j < x; j++) {
   x = x + y;
}
System.out.println(y);

mais tel quel, il imprime yet ne boucle pas indéfiniment. Je ne peux pas comprendre pourquoi. Cependant, lorsque j'ajuste le code de la manière suivante:

int x = 5;
int y = 9;
for (int j = 0; j < x; j++) {
    x = x + y;
    System.out.println(y);
}
System.out.println(y);

Cela devient une boucle sans fin et je ne sais pas pourquoi. Java reconnaît-il sa boucle sans fin et l'ignore dans la première situation, mais doit-il exécuter un appel de méthode dans la seconde pour qu'il se comporte comme prévu? Confus :)


4
La deuxième boucle est sans fin car la limite supérieure xcroît plus vite que la variable de boucle j. En d'autres termes, jn'atteindra jamais une limite supérieure, donc la boucle fonctionnera "pour toujours". Eh bien, pas pour toujours, vous aurez probablement un débordement à un moment donné.
Tim Biegeleisen

75
Ce n'est pas une boucle sans fin, c'est juste qu'il faut 238609294 fois pour que la boucle sorte de la boucle for dans le premier cas et la deuxième fois, il imprime la valeur de y238609294 fois
N00b Pr0grammer

13
réponse en un mot: débordement
qwr

20
De manière amusante, System.out.println(x)au lieu de yla fin aurait montré instantanément quel était le problème
JollyJoker

9
@TeroLahtinen non, ce ne serait pas le cas. Lisez la spécification du langage Java si vous avez des doutes sur le type int. Il est indépendant du matériel.
9ilsdx 9rvj 0lo

Réponses:


161

Les deux exemples ne sont pas sans fin.

Le problème est la limitation du inttype en Java (ou à peu près dans n'importe quel autre langage courant). Lorsque la valeur de xatteint 0x7fffffff, l'ajout d'une valeur positive entraînera un débordement et le xdeviendra négatif, donc inférieur à j.

La différence entre la première et la deuxième boucle est que le code interne prend beaucoup plus de temps et qu'il faudrait probablement plusieurs minutes avant que les xdébordements ne se produisent . Pour le premier exemple, cela peut prendre moins d'une seconde ou très probablement le code sera supprimé par l'optimiseur car il n'a aucun effet.

Comme mentionné dans la discussion, le temps dépendra fortement de la façon dont le système d'exploitation met en mémoire tampon la sortie, s'il sort vers l'émulateur de terminal, etc., de sorte qu'il peut être beaucoup plus élevé que quelques minutes.


48
Je viens d'essayer un programme (sur mon ordinateur portable) qui imprime une ligne en boucle. Je l'ai chronométré et il a pu imprimer environ 1000 lignes / seconde. Sur la base du commentaire de N00b selon lequel la boucle s'exécutera 238609294 fois, il faudra environ 23861 secondes pour que la boucle se termine - plus de 6,6 heures. Un peu plus que "plusieurs minutes".
ajb

11
@ajb: dépend de l'implémentation. IIRC println()sur Windows est une opération bloquante, alors que sur (certains?) Unix, il est mis en mémoire tampon, donc va beaucoup plus vite. Essayez également d'utiliser print(), quels tampons jusqu'à ce qu'il atteigne un \n(ou que le tampon se remplisse, ou flush()soit appelé)
BlueRaja - Danny Pflughoeft

6
Cela dépend également du terminal affichant la sortie. Voir stackoverflow.com/a/21947627/53897 pour un exemple extrême (où le ralentissement était dû à l'habillage de mots)
Thorbjørn Ravn Andersen

1
Oui, il est mis en mémoire tampon sous UNIX, mais il bloque toujours. Une fois que la mémoire tampon de 8K environ est remplie, elle se bloquera jusqu'à ce qu'il y ait de la place. La vitesse dépendra fortement de la rapidité avec laquelle il est consommé. La redirection de la sortie vers / dev / null sera la plus rapide, mais son envoi au terminal, la valeur par défaut, nécessitera des mises à jour graphiques de l'écran et beaucoup plus de puissance de calcul car cela rend les polices qui le ralentissent.
penguin359

2
@Zbynek oh, probablement oui, mais cela me rappelle que les E / S des terminaux seront généralement tamponnées en ligne, pas bloquées, donc il est fort probable que chaque println entraînera un appel système ralentissant davantage le boîtier du terminal.
penguin359

33

Puisqu'ils sont déclarés comme int, une fois qu'il atteint la valeur maximale, la boucle se cassera car la valeur x deviendra négative.

Mais lorsque System.out.println est ajouté à la boucle, la vitesse d'exécution devient visible (car la sortie vers la console ralentira la vitesse d'exécution). Cependant, si vous laissez le 2ème programme (celui avec syso à l'intérieur de la boucle) s'exécuter assez longtemps, il devrait avoir le même comportement que le premier (celui sans syso dans la boucle).


21
Les gens ne réalisent pas à quel point le spam sur la console peut ralentir leur code.
user9993

13

Il peut y avoir deux raisons à cela:

  1. Java optimise la forboucle et comme il n'y a aucune utilisation d' xaprès la boucle, supprime simplement la boucle. Vous pouvez vérifier cela en mettant une System.out.println(x);instruction après la boucle.

  2. Il est possible que Java n'optimise pas réellement la boucle et qu'il exécute le programme correctement et finisse par xdevenir trop volumineux intet déborder. Un débordement d'entier rendra probablement l'entier xnégatif qui sera plus petit que j et il sortira donc de la boucle et affichera la valeur de y. Cela peut également être vérifié en ajoutant System.out.println(x);après la boucle.

De plus, même dans le premier cas, un débordement se produira éventuellement, ce qui le rendra dans le second cas, de sorte que ce ne sera jamais une véritable boucle sans fin.


14
Je choisis la porte numéro 2.
Robby Cornelissen

Vrai. Il est allé sur l'échelle négative et est sorti de la boucle. Mais a sysoutest si lent à ajouter l'illusion d'une boucle infinie.
Pavan Kumar

4
1. Serait un bug. Les optimisations du compilateur ne sont pas autorisées à modifier le comportement d'un programme. S'il s'agissait d'une boucle infinie, alors le compilateur peut optimiser tout ce qu'il veut, cependant, le résultat doit toujours être une boucle infinie. La vraie solution est que l'OP se trompe: aucun des deux n'est une boucle infinie, l'un fait juste plus de travail que l'autre, donc cela prend plus de temps.
Jörg W Mittag

1
@ JörgWMittag Dans ce cas, x est une variable locale sans relation avec autre chose. En tant que tel, il est possible qu'il soit optimisé. Mais il faut regarder le bytecode pour déterminer si c'est le cas, ne jamais simplement supposer que le compilateur a fait quelque chose comme ça.
J'espère

1

Ce ne sont pas tous les deux des boucles infinies, initialement j = 0, tant que j <x, j augmente (j ++), et j est un entier donc la boucle fonctionnerait jusqu'à ce qu'elle atteigne la valeur maximale puis déborde (un débordement d'entier est la condition qui se produit lorsque le résultat d'une opération arithmétique, telle que la multiplication ou l'addition, dépasse la taille maximale du type entier utilisé pour le stocker.). pour le deuxième exemple, le système imprime simplement la valeur de y jusqu'à ce que la boucle se brise.

si vous cherchez un exemple de boucle sans fin, cela devrait ressembler à ceci

int x = 6;

for (int i = 0; x < 10; i++) {
System.out.println("Still Looping");
}

car (x) n'atteindrait jamais la valeur de 10;

vous pouvez également créer une boucle infinie avec une double boucle for:

int i ;

  for (i = 0; i <= 10; i++) {
      for (i = 0; i <= 5; i++){
         System.out.println("Repeat");   
      }
 }

cette boucle est infinie car la première boucle for dit i <10, ce qui est vrai donc elle entre dans la deuxième boucle for et la deuxième boucle for augmente la valeur de (i) jusqu'à ce qu'elle soit == 5. Ensuite, elle passe dans la première boucle for à nouveau parce que i <10, le processus se répète car il se réinitialise après la deuxième boucle for


1

C'est une boucle finie car une fois que la valeur de xdépasse 2,147,483,647(qui est la valeur maximale de an int), xelle deviendra négative et ne sera plus jsupérieure, que vous imprimiez y ou non.

Vous pouvez simplement changer la valeur de yà 100000et imprimer ydans la boucle et la boucle se cassera très bientôt.

La raison pour laquelle vous pensez qu'il est devenu infini est que le System.out.println(y);code a été exécuté beaucoup plus lentement que sans aucune action.


0

Problème intéressant En fait, dans les deux cas, la boucle n'est pas sans fin

Mais la principale différence entre eux est quand il se terminera et combien de temps xil faudra pour dépasser la intvaleur maximale , c'est- 2,147,483,647à-dire qu'il atteindra l'état de débordement et que la boucle se terminera.

La meilleure façon de comprendre ce problème est de tester un exemple simple et de conserver ses résultats.

Exemple :

for(int i = 10; i > 0; i++) {}
System.out.println("finished!");

Production:

finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Après avoir testé cette boucle infinie, il faudra moins d'une seconde pour se terminer.

for(int i = 10; i > 0; i++) {
    System.out.println("infinite: " + i);
}
System.out.println("finished!");

Production:

infinite: 314572809
infinite: 314572810
infinite: 314572811
.
.
.
infinite: 2147483644
infinite: 2147483645
infinite: 2147483646
infinite: 2147483647
finished!
BUILD SUCCESSFUL (total time: 486 minutes 25 seconds)

Sur ce cas de test, vous remarquerez une énorme différence dans le temps nécessaire pour terminer et terminer l'exécution du programme.

Si vous n'êtes pas patient, vous penserez que cette boucle est sans fin et ne se terminera pas, mais en fait, il faudra des heures pour se terminer et atteindre l'état de débordement à la ivaleur.

Enfin, nous avons conclu après avoir mis l'instruction print dans la boucle for que cela prendrait beaucoup plus de temps que la boucle dans le premier cas sans instruction print.

Ce temps nécessaire pour exécuter le programme dépend des spécifications de votre ordinateur, en particulier de la puissance de traitement (capacité du processeur), du système d'exploitation et de votre IDE qui compile le programme.

Je teste ce cas sur:

Lenovo 2,7 GHz Intel Core i5

Système d'exploitation: Windows 8.1 64x

IDE: NetBeans 8.2

Il faut environ 8 heures (486 minutes) pour terminer le programme.

Vous pouvez également remarquer que l'incrément de pas dans la boucle for i = i + 1est un facteur très lent pour atteindre la valeur max int.

Nous pouvons changer ce facteur et accélérer l'incrémentation des pas afin de tester la boucle en moins de temps.

si nous le mettons i = i * 10et le testons:

for(int i = 10; i > 0; i*=10) {
           System.out.println("infinite: " + i);
}
     System.out.println("finished!");

Production:

infinite: 100000
infinite: 1000000
infinite: 10000000
infinite: 100000000
infinite: 1000000000
infinite: 1410065408
infinite: 1215752192
finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Comme vous le voyez, c'est très rapide par rapport à la boucle précédente

cela prend moins d'une seconde pour se terminer et terminer l'exécution du programme.

Après cet exemple de test, je pense qu'il devrait clarifier le problème et prouver la validité de Zbynek Vyskovsky - la réponse de kvr000 , aussi ce sera la réponse à cette question .

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.