500 votes

Pourquoi cela, allez dans une boucle infinie ?

Je suis un enseignant, et hier, un élève a écrit 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 devrait avoir écrit juste x++ ou x=x+1, mais sur x = x++; il convient tout d'abord de l'attribut x de lui-même, et, plus tard, incrémenter x. Pourquoi ne x continuer avec 0 comme valeur?

--mise à jour

Voici le pseudo-code:

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

358voto

Dan Tao Points 60518

Note: a l'Origine, j'ai posté le code C# dans cette réponse, pour les fins de l'illustration, depuis C# permet de passer int des paramètres par référence avec l' ref mot-clé. J'ai décidé de le mettre à jour avec la réalité juridique de code Java à l'aide de la première MutableInt classe que j'ai trouvé sur Google pour en quelque sorte une approximation de ce qu' ref t en C#. Je ne peux pas vraiment dire si cela aide ou blesse la réponse. Je dirai que, personnellement, je n'ai pas fait beaucoup de développement Java, de sorte que pour tout ce que je sais, il pourrait être beaucoup plus idiomatiques façons d'illustrer ce point.


Peut-être que si nous écrivons une méthode pour faire l'équivalent de ce qu' x++ qu'il ne fera plus claire à ce sujet.

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

Droit? Incrémenter la valeur passée et retourner à la valeur originale: c'est la définition de l'opérateur de post-incrémentation.

Maintenant, nous allons voir comment ce comportement se joue dans votre exemple de code:

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

postIncrement(x) fait quoi? Incréments x, oui. Et puis retourne ce x a été avant de l'incrément. Cette valeur de retour, puis se voit assignée x.

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

Cela peut être plus clair encore si nous ré-écrivez ci-dessus:

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 l'affectation ci-dessus avec y", vous pouvez voir qu'il incrémente x et version ultérieure des attributs de il y" me semble confus. Il n'est pas x qui est affecté à l' y; c'est la valeur anciennement attribuée à l' x. Vraiment, l'injection d' y rend les choses ne sont pas différents de scénario ci-dessus; nous avons simplement obtenu:

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: x = x++ effectivement ne change pas la valeur de x. Il provoque toujours à x les valeurs de x0, alors x0 + 1, puis x0 nouveau.


Mise à jour: par ailleurs, de peur de vous en doutez x est affectée à 1 "entre" l'opération d'incrémentation et de l'affectation dans l'exemple ci-dessus, j'ai jeté un rapide de démonstration pour illustrer le fait que cette valeur intermédiaire ne fait "exister", mais il ne sera jamais "vu" sur le thread qui s'exécute.

La démo appels x = x++; dans une boucle alors qu'un thread séparé en permanence imprime la valeur de x à 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 du programme ci-dessus est de sortie. Avis de l'alternance irrégulière de deux 1s et 0s.

Départ thread d'arrière-plan...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1

172voto

axtavt Points 126632

x = x++ fonctionne de la façon suivante:

  • D'abord, il évalue l'expression x++. L'évaluation de cette expression qui produit une valeur d'expression (qui est la valeur de x avant de remonter) et incrémente x.
  • Plus tard, il assigne la valeur de l'expression d' x, en écrasant incrémenté de la valeur.

Ainsi, la séquence des événements qui ressemble à la suivante (c'est un vrai décompilé bytecode, telle que produite par javap -c, avec mes commentaires):

 8: iload_1 // n'oubliez pas de valeur de x dans la pile
 9: iinc 1, 1 // Incrémenter x (ne pas changer la pile)
 12: istore_1 // Écrire remebered valeur de la pile à x

Pour comparaison, x = ++x:

 8: iinc 1, 1 // Incrémenter x
 11: iload_1 // Poussez la valeur de x sur la pile
 12: istore_1 // Pop de la valeur de la pile à x

105voto

codaddict Points 154968

Cela se produit car la valeur de x n'est pas incrémentée à tous.

x = x++;

est équivalent à

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

Explication:

Regardons les octets de code pour cette opération. Considérons un exemple de classe:

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

Maintenant, l'exécution de la classe désassembleur 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, la machine virtuelle Java 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 surgi pour effectuer l'opération. Il y a aussi une autre structure de données, généralement un tableau pour stocker les variables locales. Les variables locales sont donnés les id qui sont les indices de la matrice.

Penchons-nous sur les mnémoniques en main() méthode:

  • iconst_0: La valeur de la constante 0 est poussé sur la pile.
  • istore_1: L'élément supérieur de la la pile est sauté et stockées dans la variable locale avec index 1
    qui est - x.
  • iload_1 : La valeur à l' emplacement 1 qui est la valeur de x qui est - 0, est poussé dans le la pile.
  • iinc 1, 1 : La valeur à l' l'emplacement de la mémoire 1est incrémenté par 1. Donc, x devient maintenant 1.
  • istore_1 : La valeur en haut de la pile est stocké sur la mémoire emplacement 1. C'est - 0 est attribué d' x d'écraser sa valeur incrémentée.

Dès lors, la valeur de x n'est pas le changement qui en résulte dans la boucle infinie.

54voto

Jaydee Points 3388
  1. Préfixe notation s'incrémente la variable AVANT de l'expression est évaluée.
  2. Postfix notation s'incrémente APRÈS l'évaluation de l'expression.

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

Donc, x=x++; devrait évaluer comme suit

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

34voto

Robert Munteanu Points 31558

Aucune des réponses où bien sur, alors voilà:

Lorsque vous êtes en train de rédiger int x = x++, vous n'êtes pas à l'attribution d' x d'être lui-même à la nouvelle valeur que vous attribuez x de la valeur de retour de l' x++ expression. Qui se trouve être la valeur d'origine de l' x, comme évoqué dans Colin Cochrane réponse .

Pour le plaisir, pour 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 l' x, qui est de zéro. Mais plus tard, lors de la lecture de la valeur de x, nous recevons la mise à jour de la valeur , qui est l'un.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X