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 :)

Omar
la source
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.

Zbynek Vyskovsky - kvr000
la source
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).

Ace Zachary
la source
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.

Ashok Vishnoi
la source
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

Kennedy
la source
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.

Joe Cheng
la source
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 .

Oghli
la source