Pourquoi cela ne va dans une boucle infinie?

j'ai le code suivant:

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

nous savons qu'il aurait dû écrire juste x++ ou x=x+1 , mais sur x = x++ il devrait d'abord attribuer x à lui-même, et plus tard l'augmenter. Pourquoi x continue avec 0 comme valeur?

--mise à jour

voici le code postal:

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

}

je vais lire à propos de la instructions pour essayer de comprendre...

481
demandé sur Tom Brito 2010-09-30 18:07:51
la source

27 ответов

Note : à l'origine, j'ai affiché le code C# dans cette réponse à des fins d'illustration, puisque C# permet de passer les paramètres int par référence avec le mot-clé ref . J'ai décidé de le mettre à jour avec le code Java juridique réel en utilisant le premier MutableInt classe que j'ai trouvé sur Google pour sorte d'approximation ce que ref fait dans C#. Je ne peux pas vraiment dire si cela aide ou blesse la réponse. Je dirai que, personnellement, je Je n'ai pas fait beaucoup de développement Java; donc, pour ce que je sais, il pourrait y avoir des façons plus idiomatiques d'illustrer ce point.


peut-être que si nous écrivons une méthode pour faire l'équivalent de ce que x++ fait, cela rendra cela plus clair.

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

C'est ça? Incrémenter la valeur passée et retourner la valeur originale: c'est la définition de l'opérateur post-incrément.

maintenant, voyez comment ce comportement se joue dans votre code d'exemple:

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

postIncrement(x) fait quoi? Incréments x , Oui. Et puis renvoie ce que x était avant l'incrément . Cette valeur de retour est ensuite assignée à x .

ainsi l'ordre des valeurs attribuées à x est 0, puis 1, Puis 0.

cela pourrait être encore plus clair si nous réécrire ce qui précède:

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.

votre fixation sur le fait que lorsque vous remplacez x sur le côté gauche de la mission ci-dessus avec y , "vous pouvez voir qu'il augmente d'abord x, et plus tard l'attribue à y" me semble confus. Il n'est pas x qui est attribué à y ; il est la valeur anciennement attribuée à x . Vraiment, l'injection de y rend les choses pas différentes de le scénario ci-dessus; nous avons simplement:

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.

donc c'est clair: x = x++ effectivement ne change pas la valeur de X. Il provoque toujours x d'avoir les valeurs x 0 , puis x 0 + 1, et puis x 0 encore.


mise à jour : soit dit en passant, de peur que vous doutiez que x ne soit jamais attribué à 1 "entre" le opération d'incrément et la tâche dans l'exemple ci-dessus, j'ai rassemblé une démonstration rapide pour illustrer que cette valeur intermédiaire "existe", bien qu'elle ne sera jamais "vue" sur le thread d'exécution.

la démo appelle x = x++; en boucle alors qu'un thread distinct imprime en continu la valeur de x sur la console.

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);
        }
    }
}

ci-dessous est un extrait de la sortie du programme ci-dessus. Avis de l'alternance irrégulière de 1 et de 0.

Starting background thread...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1
348
répondu Dan Tao 2012-08-30 04:06:47
la source

x = x++ fonctionne de la manière suivante:

  • tout d'abord, il évalue l'expression x++ . L'évaluation de cette expression produit une valeur d'expression (qui est la valeur de x avant incrément) et des incréments x .
  • plus tard , il assigne la valeur d'expression à x , valeur incrémentée d'écrasement.

ainsi, la séquence des événements ressemble à ce qui suit (c'est un bytecode décompilé réel, tel que produit par javap -c , avec mes commentaires):

   8:   iload_1         // Remember current value of x in the stack
   9:   iinc    1, 1    // Increment x (doesn't change the stack)
   12:  istore_1        // Write remebered value from the stack to x

pour comparaison, x = ++x :

   8:   iinc    1, 1    // Increment x
   11:  iload_1         // Push value of x onto stack
   12:  istore_1        // Pop value from the stack to x
168
répondu axtavt 2010-09-30 18:49:31
la source

cela se produit parce que la valeur de x n'est pas incrémentée du tout.

x = x++;

est équivalent à

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

explication:

regardons le code octet pour cette opération. Considérons une classe d'échantillon:

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

en cours D'exécution de la classe démontage sur ce que nous obtenons:

$ 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
}

maintenant le Java VM est pile ce qui signifie que pour chaque opération, les données seront poussés sur la pile et de la pile, les données de la pop pour effectuer l'opération. Il y a aussi une autre structure de données, typiquement un tableau pour stocker les variables locales. Les variables locales sont données des ID qui sont juste les index du tableau.

regardons le mnémonique dans main() méthode:

  • iconst_0 : la constante valeur 151970920" est poussé sur la pile.
  • istore_1 : L'élément supérieur de la stack est sorti et stocké dans le variable locale avec indice 1

    qui est x .
  • iload_1 : la valeur au emplacement 1 qui est la valeur de x qui est 0 , est poussé sur la pile.
  • iinc 1, 1 : la valeur au l'emplacement de la mémoire 1 est incrémenté de 1 . Ainsi x devient maintenant 1 .
  • istore_1 : La valeur en haut de la pile est stockée à l'emplacement mémoire 1 . C'est 0 est attribué x écraser sa valeur incrémentée.

par conséquent la valeur de x ne change pas résultant dans la boucle infinie.

104
répondu codaddict 2018-07-25 20:37:58
la source
  1. la notation du préfixe incrémentera la variable avant que l'expression ne soit évaluée.
  2. la notation Postfix augmentera après l'évaluation de l'expression.

Cependant " = "a un faible niveau de priorité de l'opérateur que" ++ ".

So x=x++; doit être évalué comme suit

  1. x préparé pour affectation (évalué)
  2. x incrémenté
  3. valeur antérieure de x attribuée à x .
52
répondu Jaydee 2010-10-02 02:02:56
la source

Aucune des réponses où l'endroit assez, voilà:

quand vous écrivez int x = x++ , vous n'assignez pas x pour être lui-même à la nouvelle valeur, vous assignez x pour être la valeur de retour de l'expression x++ . Ce qui se trouve être la valeur originale de x , comme indiqué dans réponse de Colin Cochrane .

pour le plaisir, tester le code suivant:

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

le résultat sera

0
1

la valeur de retour de l'expression est la valeur initiale de x , qui est zéro. Mais plus tard , en lisant la valeur de x , nous recevons la valeur mise à jour, c'est-à-dire une.

34
répondu Robert Munteanu 2017-05-23 15:18:01
la source

Il a déjà été expliqué par d'autres. J'ai juste inclus les liens vers les sections de spécification Java pertinentes.

x = x++ est une expression. Java suivra l'ordre d'évaluation . Il évaluera d'abord l'expression x++, qui incrémentera x et définira la valeur du résultat à la valeur précédente de x . Ensuite, assignera le résultat de l'expression à la variable X. À la fin, x est de retour à sa valeur précédente.

29
répondu plodoc 2010-09-30 19:33:21
la source

Cette déclaration:

x = x++;

évalue comme ceci:

  1. Push x on the stack;
  2. Incrémenter x ;
  3. Pop x à partir de la pile.

de sorte que la valeur est inchangée. Comparez cela à:

x = ++x;

qui se lit comme suit:

  1. Increment x ;
  2. Push x on the stack;
  3. Pop x à partir de la pile.

Ce que vous voulez, c'est:

while (x < 3) {
  x++;
  System.out.println(x);
}
18
répondu cletus 2010-10-07 04:38:53
la source

, La réponse est assez simple. Il s'agit de l'ordre dans lequel les choses sont évaluées. x++ renvoie la valeur x puis les incréments x .

par conséquent, la valeur de l'expression x++ est 0 . Donc vous assignez x=0 à chaque fois dans la boucle. Certainement x++ augmente cette valeur, mais cela se produit avant l'affectation.

10
répondu Mike Jones 2012-04-07 00:22:57
la source

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

les opérateurs d'incrément/décrément peuvent être appliqué avant (préfixe) ou après (postfix), l'opérande. Code résultat++; et résultat++; les deux se termineront en Résultat être incrémenté par un. La seule différence est que le préfixe version (++) évalue à l' valeur incrémentée, postfix évaluation de la version (résultat++) à la valeur originale . Si vous êtes juste une simple incrémenter/décrémenter, il n'a pas vraiment d' peu importe la version que vous choisissez. Mais si vous utilisez cet opérateur dans le cadre d'un plus grande expression, celle que vous choisissez peut faire une grande différence.

pour illustrer, essayez ce qui suit:

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

qui affichera 1 et 0.

8
répondu Colin Cochrane 2010-09-30 18:42:30
la source

vous avez effectivement le comportement suivant.

  1. saisissez la valeur de x (qui est 0) comme "le résultat" du côté droit
  2. incrémente la valeur de x (donc x est maintenant 1)
  3. assignez le résultat du côté droit (qui a été sauvegardé comme 0) à x (x est maintenant 0)

l'idée étant que l'opérateur de post-incrément (x++) incrémente cette variable en question après avoir retourné sa valeur pour une utilisation dans l'équation, il est utilisé dans.

Edit: Ajouter un petit peu à cause du commentaire. Considérez-la comme suit.

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.
7
répondu RHSeeger 2012-04-07 00:17:40
la source

vous n'avez pas vraiment besoin du code machine pour comprendre ce qui se passe.

selon les définitions:

  1. l'opérateur d'assignation évalue l'expression du côté droit et la stocke dans une variable temporaire.

    1.1. La valeur actuelle de x est copiée dans cette variable temporaire

    1.2. x est incrémenté maintenant.

  2. La variable temporaire est alors copiée dans le côté gauche de l'expression, qui est x par hasard! C'est pourquoi l'ancienne valeur de x est de nouveau copié sur lui-même.

c'est assez simple.

7
répondu houman001 2012-04-07 00:19:28
la source

c'est parce qu'il n'est jamais incrémenté dans ce cas. x++ utilisent la valeur avant incrémentation comme sur ce cas, il sera comme:

x = 0;

mais si vous faites ++x; cela va augmenter.

5
répondu mezzie 2012-04-07 00:13:36
la source

la valeur reste à 0 parce que la valeur de x++ est 0. Dans ce cas, peu importe si la valeur de x est augmentée ou non, la commande x=0 est exécutée. Cela écrasera la valeur incrémentée temporaire de x (qui était 1 pour un"très court temps").

3
répondu Progman 2010-09-30 18:15:55
la source

cela fonctionne comme vous attendez l'autre à. C'est la différence entre préfixe et postfix.

int x = 0; 
while (x < 3)    x = (++x);
1
répondu patrick 2010-09-30 18:39:12
la source

pense à x++ comme un appel de fonction qui "retourne" ce que X était avant l'incrément (c'est pourquoi il est appelé un post-incrément).

donc l'ordre d'opération est:

1: cache la valeur de x avant d'augmenter

2: incrément x

3: retourner la valeur mise en cache (x avant qu'elle ne soit incrémentée)

4: la valeur de retour est attribuée à x

1
répondu jhabbott 2010-09-30 18:58:27
la source

quand le ++ est sur le rhs, le résultat est retourné avant que le nombre soit incrémenté. Passer à ++x et ça aurait été bien. Java aurait optimisé pour effectuer une seule opération (l'affectation de x à x) plutôt que de l'incrément.

1
répondu Steven 2010-10-01 03:15:11
la source

aussi loin que je puisse voir, l'erreur se produit, en raison de l'affectation qui dépasse la valeur incrémentée, avec la valeur avant l'incrémentation, c.-à-d. il défait l'incrément.

plus Précisément, le "x++" l'expression a la valeur de " x " avant d'incrémenter par opposition à "++x", qui a la valeur de " x " après incrémentation.

si vous êtes intéressé à étudier le bytecode, nous allons jeter un oeil aux trois lignes en question:

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

7: iload_1 # va mettre la valeur de la 2e variable locale sur la pile

8: iinc 1,1 # incrémentera la 2ème variable locale avec 1, notez qu'elle laisse la pile intacte!

9: istore_1 # pop le haut de la pile et enregistrer la valeur de cet élément à la 2e variable locale

(Vous pouvez lire les effets de chaque instruction JVM ici )

c'est pourquoi le code ci-dessus sera bouclé indéfiniment, alors que la version avec ++x ne le sera pas. Le bytecode pour ++x devrait être très différent, pour autant que je me souvienne du compilateur Java 1.3 que j'ai écrit il y a un peu plus d'un an, le bytecode devrait être quelque chose comme ceci:

iinc 1,1
iload_1
istore_1

il suffit donc de changer les deux premières lignes, change la sémantique de sorte que la valeur laissée sur le dessus de la pile, après l'incrément (i.e. la 'valeur' de l'expression) est la valeur après l'incrémentation.

1
répondu micdah 2010-10-02 05:16:25
la source
    x++
=: (x = x + 1) - 1

:

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

attendu que

   ++x
=: x = x + 1

:

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

bien sûr, le résultat final est le même que juste x++; ou ++x; sur une ligne.

1
répondu Paulpro 2011-11-26 15:08:10
la source

Phrase

x = x++;

"se traduit" par

x = x;
x = x + 1;

C'est ça.

1
répondu Willmore 2016-09-12 08:17:59
la source
 x = x++; (increment is overriden by = )

en raison de la déclaration ci-dessus x n'atteint jamais 3;

0
répondu Praveen Prasad 2010-09-30 21:30:43
la source

je me demande s'il y a quelque chose dans la spécification Java qui définit précisément le comportement de ceci. (L'implication évidente de cette déclaration étant que je suis trop paresseux pour vérifier.)

Note du bytecode de Tom, les lignes clés sont 7, 8 et 11. La ligne 7 Charge x dans la pile de calcul. Ligne 8 incréments X. La ligne 11 stocke la valeur de la pile de nouveau à X. Dans les cas normaux où vous n'attribuez pas des valeurs à eux-mêmes, Je ne pense pas qu'il y aurait une raison pour vous ne pouvait pas charger, stocker, puis incrémenter. Vous obtenez le même résultat.

comme, supposons que vous aviez un cas plus normal où vous avez écrit quelque chose comme: z=(x++)+(y++);

S'il est dit (pseudocode pour sauter des détails techniques)

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

ou

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

ne devrait pas être pertinent. Je pense que l'une ou l'autre mise en œuvre devrait être valide.

je serais extrêmement prudent code d'écriture qui dépend de ce comportement. Il semble très dépendant de la mise en œuvre, entre-les-craques-dans-les-spécifications pour moi. La seule fois où cela ferait une différence est si vous avez fait quelque chose de fou, comme l'exemple ici, ou si vous aviez deux threads en cours d'exécution et dépendaient de l'ordre d'évaluation dans l'expression.

0
répondu Jay 2010-09-30 23:55:18
la source

je pense que parce Qu'en Java ++ a une priorité plus élevée que = (assignation)...Fait-il? Regardez http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html ...

de la même façon si vous écrivez x=x+1...+ a une priorité plus élevée que = (affectation)

0
répondu cubob 2010-10-01 11:00:54
la source

l'expression x++ est évaluée à x . La partie ++ affecte la valeur après évaluation , et non après déclaration . donc x = x++ est effectivement traduit en

int y = x; // evaluation
x = x + 1; // increment part
x = y; // assignment
0
répondu tia 2010-10-01 20:38:27
la source

avant d'augmenter la valeur par un, la valeur est attribuée à la variable.

0
répondu kumara 2012-04-07 00:20:15
la source

ça arrive parce que c'est incrémenté. Cela signifie que la variable est incrémentée après l'expression est évaluée.

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

x est maintenant 10, mais y est 9, la valeur de x avant qu'il a été incrémenté.

voir plus dans définition de L'augmentation D'échelon de mission .

0
répondu BrunoBrito 2012-04-07 00:21:55
la source

cochez le code ci-dessous,

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

la sortie sera,

temp = 0
x = 0

post increment signifie incrément la valeur et retourner la valeur avant l'incrément . C'est pourquoi la valeur temp est 0 . Alors que faire si temp = i et c'est dans une boucle (sauf pour la première ligne de code). tout comme dans la question !!!!

0
répondu prime 2015-05-29 11:31:01
la source

L'opérateur d'incrémentation est appliqué à la même variable que vous attribuez à. C'est chercher les ennuis. Je suis sûr que vous pouvez voir la valeur de votre variable x pendant l'exécution de ce programme.... ça devrait expliquer pourquoi la boucle ne s'arrête jamais.

-1
répondu SIVAKUMAR.J 2010-12-10 14:29:09
la source