web-dev-qa-db-fra.com

Pourquoi cela va-t-il 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 s’attribuer x à lui-même, puis l’incrémenter. Pourquoi x continue-t-il avec 0 comme valeur?

- mise à jour

Voici le bytecode:

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 sur le instructions pour essayer de comprendre ...

489
Tom Brito

Note : À l'origine, j'ai posté du code C # dans cette réponse à des fins d'illustration, car C # vous permet de passer int paramètres par référence à l'aide du ref mot clé. J'ai décidé de le mettre à jour avec le code légal Java) actuel en utilisant le premier MutableInt classe trouvée sur Google pour trier approximativement ce que ref fait en C # .Je ne sais pas vraiment si cela aide ou fait mal la réponse. Je dirai que personnellement, je n'ai pas fait grand chose Java développement; donc pour tout ce que je sachez qu'il pourrait y avoir des moyens beaucoup plus idiomatiques pour illustrer ce point.


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

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

Droite? Incrémenter la valeur passée et retourner la valeur d'origine: c'est la définition de l'opérateur postincrément.

Voyons maintenant comment cela se passe dans votre exemple de code:

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

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

Donc, l'ordre des valeurs assignées à x est 0, puis 1, puis 0.

Cela pourrait être plus clair encore si nous réécrivions 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 à gauche de l'affectation ci-dessus par y, vous pouvez voir qu'elle incrémente d'abord x et l'attribue ensuite à y "me frappe comme confus. Ce n'est pas x qui est assigné à y; c'est la valeur précédemment attribuée à x. En réalité, injecter y ne fait pas la différence avec 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.

Il est donc clair que x = x++ Ne change pas la valeur de x. Il fait toujours en sorte que x ait les valeurs x, alors x + 1, puis x encore.


Update : Incidemment, de peur que vous doutiez que x soit jamais affecté à 1 "entre" l'opération d'incrémentation et l'affectation dans l'exemple ci-dessus. , J'ai réuni une démo rapide pour illustrer le fait que cette valeur intermédiaire "existe", bien qu'elle ne soit jamais "vue" sur le thread en cours d'exécution.

La démo appelle x = x++; Dans une boucle, tandis qu'un thread séparé imprime en permanence 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 un extrait de la sortie du programme ci-dessus. Notez l'occurrence irrégulière des 1 et des 0.

 Début du fil de fond ... 
 0 
 0 
 1 
 1 
 0 
 0 
 0 
 0 
 0 
 0 
 0 
 0 
 0 
 0 
 1 
 0 
 1 
352
Dan Tao

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

  • 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 incrémente x.
  • Plus tard, il assigne la valeur de l'expression à x, écrasant ainsi la valeur incrémentée.

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

   8: iload_1 // Mémoriser la valeur actuelle de x dans la pile 
 9: iinc 1, 1 // Incrémenter x (ne modifie pas la pile) 
 12: istore_1 // Écrire une valeur mémorisée dans la pile à x 

En comparaison, x = ++x:

   8: iinc 1, 1 // Incrément x 
 11: iload_1 // Envoie la valeur de x à la pile 
 12: istore_1 // Ajoute une valeur de la pile à x
169
axtavt

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 d'octet pour cette opération. Considérons un exemple de classe:

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

Maintenant, en exécutant le désassembleur de classe, 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
}

La machine virtuelle Java est maintenant basée sur la pile, ce qui signifie que pour chaque opération, les données seront placées dans la pile et à partir de la pile, les données sortiront pour effectuer l'opération. Il existe également une autre structure de données, généralement un tableau pour stocker les variables locales. Les variables locales reçoivent des identifiants qui ne sont que les index du tableau.

Regardons la méthode mnémoniques dans main():

  • iconst_0: La valeur constante 0 Est appliquée à la pile.
  • istore_1: L'élément supérieur de la pile est sorti et stocké dans la variable locale avec index 1
    qui est x.
  • iload_1: La valeur à l'emplacement 1 Qui correspond à la valeur de x qui correspond à 0 Est insérée dans la pile.
  • iinc 1, 1: La valeur à l'emplacement de mémoire 1 Est incrémentée de 1. Donc, x devient maintenant 1.
  • istore_1: La valeur en haut de la pile est stockée dans l'emplacement de mémoire 1. C'est-à-dire que 0 Est attribué à x écrasement sa valeur incrémentée.

Par conséquent, la valeur de x ne change pas, ce qui entraîne la boucle infinie.

104
codaddict
  1. La notation préfixe incrémentera la variable AVANT que l'expression soit évaluée.
  2. La notation Postfix sera incrémentée APRÈS l’évaluation de l’expression.

Toutefois "= "a une priorité inférieure à celle de" ++ ".

Alors x=x++; devrait évaluer comme suit

  1. x préparé pour l'affectation (évalué)
  2. x incrémenté
  3. Valeur précédente de x attribuée à x.
52
Jaydee

Aucune des réponses n'était assez bien placée, alors voici:

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

Pour le plaisir, testez 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, lors de la lecture de la valeur de x, nous recevons la valeur mise à jour, c'est-à-dire une.

34
Robert Munteanu

Cela a déjà été bien expliqué par d'autres. Je viens d’inclure les liens vers les sections correspondantes de la spécification Java).

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

29
plodoc

Cette déclaration:

x = x++;

évalue comme ceci:

  1. Poussez x sur la pile;
  2. Incrémenter x;
  3. Pop x de la pile.

Donc, la valeur est inchangée. Comparez cela à:

x = ++x;

qui évalue comme:

  1. Incrémenter x;
  2. Poussez x sur la pile;
  3. Pop x de la pile.

Ce que tu veux c'est:

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

La réponse est assez simple. Cela a à voir avec l'ordre dans lequel les choses sont évaluées. x++ renvoie la valeur x puis incrémente x.

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

10
Mike Jones

De http://download.Oracle.com/javase/tutorial/Java/nutsandbolts/op1.html

Les opérateurs d'incrémentation/décrémentation peuvent être appliqués avant (préfixe) ou après (postfixe) l'opérande. Le résultat du code ++; et ++ résultat; les deux aboutiront à un résultat incrémenté de un. La seule différence est que la version du préfixe (résultat ++) correspond à la valeur incrémentée, alors que la version de postfix (résultat ++) correspond à la valeur initiale. Si vous effectuez simplement une simple incrémentation/décrémentation, peu importe la version que vous choisissez. Mais si vous utilisez cet opérateur dans le cadre d'une expression plus grande, celle que vous choisissez peut faire une différence significative.

Pour illustrer, essayez ce qui suit:

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

Ce qui imprimera 1 et 0.

8
Colin Cochrane

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

Selon les définitions:

  1. L'opérateur d'affectation évalue l'expression de droite 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 ensuite copiée dans la partie gauche de l'expression, qui est x par hasard! C'est pourquoi l'ancienne valeur de x est à nouveau copiée sur elle-même.

C'est assez simple.

7
houman001

Vous obtenez effectivement le comportement suivant.

  1. saisir la valeur de x (qui est 0) en tant que "résultat" du côté droit
  2. incrémenter la valeur de x (donc x est maintenant 1)
  3. assigner le résultat du côté droit (qui a été enregistré comme 0) à x (x est maintenant 0)

L'idée est que l'opérateur de post-incrémentation (x ++) incrémente la variable en question APRÈS avoir renvoyé sa valeur pour qu'elle soit utilisée dans l'équation dans laquelle elle est utilisée.

Edit: Ajoute un petit peu à cause du commentaire. Considérez cela 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
RHSeeger

C'est parce qu'il ne s'incrémente jamais dans ce cas. x++ utilisera d'abord sa valeur avant de s'incrémenter comme dans ce cas, il ressemblera à:

x = 0;

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

5
mezzie

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

3
Progman

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

1
Steven

Cela fonctionne comme vous vous attendez à l'autre. C'est la différence entre prefix et postfix.

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

Pour autant que je sache, l'erreur se produit, en raison de l'affectation remplaçant la valeur incrémentée, avec la valeur antérieure à l'incrémentation, c'est-à-dire qu'elle annule l'incrément.

Plus précisément, l'expression "x ++" a la valeur "x" avant l'incrément, par opposition à "++ x", qui a la valeur "x" après l'incrémentation.

Si vous souhaitez étudier le code intermédiaire, nous allons examiner les trois lignes en question:

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

7: iload_1 # mettra la valeur de la 2ème 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 # Ouvre le haut de la pile et enregistre la valeur de cet élément dans la 2e variable locale
(Vous pouvez lire les effets de chaque instruction JVM ici )

C'est pourquoi le code ci-dessus sera mis en boucle indéfiniment, alors que la version avec ++ x ne le sera pas. Le bytecode pour ++ x devrait avoir un aspect assez différent, pour autant que je me souvienne du compilateur 1.3 Java que j'ai écrit il y a un peu plus d'un an, le bytecode devrait ressembler à ceci:

iinc 1,1
iload_1
istore_1

Donc, simplement échanger les deux premières lignes, change la sémantique de sorte que la valeur laissée en haut de la pile, après l’incrément (c’est-à-dire la "valeur" de l’expression) soit la valeur après l’incrément.

1
micdah

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

Donc, l'ordre d'opération est:
1: met en cache la valeur de x avant de l'incrémenter
2: incrément x
3: retourne la valeur en cache (x avant son incrémentation)
4: la valeur renvoyée est attribuée à x

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

Alors:

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

Tandis que

   ++x
=: x = x + 1

Alors:

   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 à part.

1
Paulpro

Ça se passe parce que c'est post incrémenté. Cela signifie que la variable est incrémentée après l'évaluation de l'expression.

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

x est maintenant 10, mais y est 9, la valeur de x avant son incrémentation.

Voir plus dans définition du post-incrément.

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

à cause de la déclaration ci-dessus, x n'atteint jamais 3;

0
Praveen Prasad

Vérifiez 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émente la valeur et renvoie la valeur avant l'incrément. C'est pourquoi la valeur temp est 0. Alors que faire si temp = i Et cela est dans une boucle (sauf pour la première ligne de code). juste comme dans la question !!!!

0
prime

Je pense que parce que dans Java ++ a une priorité plus élevée que = (affectation) ... Est-ce que c'est? Regardez http://www.cs.uwf.edu/~ eelsheik/cop2253/resources/op_precedence.html ...

De même si vous écrivez x = x + 1 ... + a une priorité plus élevée que = (affectation)

0
cubob

Avant d'incrémenter la valeur de un, la valeur est affectée à la variable.

0
kumara

Le x++ expression est évaluée à x. Le ++ La partie affecte la valeur après le evaluation, pas après le statement. alors x = x++ est effectivement traduit en

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

Je me demande s’il existe 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.)

Remarquez à partir du bytecode de Tom, les lignes clés sont 7, 8 et 11. La ligne 7 charge x dans la pile de calcul. La ligne 8 augmente x. La ligne 11 stocke la valeur de la pile dans x. Dans les cas normaux où vous ne vous assignez pas de valeurs, je ne pense pas qu'il y aurait une raison pour laquelle vous ne pourriez pas charger, stocker, puis incrémenter. Vous obtiendriez le même résultat.

Par exemple, supposons que vous ayez un cas plus normal dans lequel vous avez écrit quelque chose comme: z = (x ++) + (y ++);

Que ce soit dit (pseudo-code pour ignorer les 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

devrait être hors de propos. Soit la mise en œuvre devrait être valide, je pense.

Je serais extrêmement prudent en écrivant du code qui dépend de ce comportement. Cela me semble très dépendant de la mise en œuvre, entre les fissures. Le seul cas où cela ferait une différence serait que vous fassiez quelque chose de fou, comme dans l'exemple ci-dessous, ou que vous ayez deux threads en cours d'exécution et que vous dépendiez de l'ordre d'évaluation dans l'expression.

0
Jay