Pourquoi l’échange de valeurs avec XOR échoue-t-il lors de l’utilisation de cette forme composée?

J’ai trouvé ce code pour échanger deux nombres sans utiliser une troisième variable, en utilisant l’opérateur XOR ^ .

Code:

 int i = 25; int j = 36; j ^= i; i ^= j; j ^= i; Console.WriteLine("i:" + i + " j:" + j); //numbers Swapped correctly //Output: i:36 j:25 

Maintenant, j’ai changé le code ci-dessus en ce code équivalent.

Mon code:

 int i = 25; int j = 36; j ^= i ^= j ^= i; // I have changed to this equivalent (???). Console.WriteLine("i:" + i + " j:" + j); //Not Swapped correctly //Output: i:36 j:0 

Maintenant, je veux savoir, pourquoi mon code donne-t-il une sortie incorrecte?

EDIT: Okay, compris.

Le premier point à souligner est que vous ne devriez évidemment pas utiliser ce code de toute façon. Cependant, lorsque vous le développez, il devient équivalent à:

 j = j ^ (i = i ^ (j = j ^ i)); 

(Si nous foo.bar++ ^= i expression plus compliquée telle que foo.bar++ ^= i , il serait important que le ++ ne soit évalué qu’une seule fois, mais je crois que c’est plus simple.)

Maintenant, l’ordre d’évaluation des opérandes est toujours de gauche à droite, pour commencer, nous obtenons:

 j = 36 ^ (i = i ^ (j = j ^ i)); 

Ceci (ci-dessus) est l’étape la plus importante. Nous avons fini avec 36 comme LHS pour l’opération XOR qui est exécutée en dernier. Le LHS n’est pas “la valeur de j après l’évaluation du RHS”.

L’évaluation du RHS du ^ implique l’expression “à un niveau nested”, elle devient donc:

 j = 36 ^ (i = 25 ^ (j = j ^ i)); 

En regardant ensuite le niveau d’imbrication le plus profond, on peut substituer à la fois i et j :

 j = 36 ^ (i = 25 ^ (j = 25 ^ 36)); 

… qui devient

 j = 36 ^ (i = 25 ^ (j = 61)); 

L’affectation à j dans le RHS se produit en premier, mais le résultat est ensuite écrasé à la fin de toute façon, nous pouvons donc l’ignorer – il n’y a pas d’autres évaluations de j avant l’assignation finale:

 j = 36 ^ (i = 25 ^ 61); 

Ceci est maintenant équivalent à:

 i = 25 ^ 61; j = 36 ^ (i = 25 ^ 61); 

Ou:

 i = 36; j = 36 ^ 36; 

Qui devient:

 i = 36; j = 0; 

Je pense que c’est tout à fait correct, et ça donne la bonne réponse … Toutes mes excuses à Eric Lippert si certains détails sur l’ordre d’évaluation sont légèrement déçus 🙁

Vérifié le IL généré et il donne des résultats différents;

Le swap correct génère un simple:

 IL_0001: ldc.i4.s 25 IL_0003: stloc.0 //create a integer variable 25 at position 0 IL_0004: ldc.i4.s 36 IL_0006: stloc.1 //create a integer variable 36 at position 1 IL_0007: ldloc.1 //push variable at position 1 [36] IL_0008: ldloc.0 //push variable at position 0 [25] IL_0009: xor IL_000a: stloc.1 //store result in location 1 [61] IL_000b: ldloc.0 //push 25 IL_000c: ldloc.1 //push 61 IL_000d: xor IL_000e: stloc.0 //store result in location 0 [36] IL_000f: ldloc.1 //push 61 IL_0010: ldloc.0 //push 36 IL_0011: xor IL_0012: stloc.1 //store result in location 1 [25] 

Le swap incorrect génère ce code:

 IL_0001: ldc.i4.s 25 IL_0003: stloc.0 //create a integer variable 25 at position 0 IL_0004: ldc.i4.s 36 IL_0006: stloc.1 //create a integer variable 36 at position 1 IL_0007: ldloc.1 //push 36 on stack (stack is 36) IL_0008: ldloc.0 //push 25 on stack (stack is 36-25) IL_0009: ldloc.1 //push 36 on stack (stack is 36-25-36) IL_000a: ldloc.0 //push 25 on stack (stack is 36-25-36-25) IL_000b: xor //stack is 36-25-61 IL_000c: dup //stack is 36-25-61-61 IL_000d: stloc.1 //store 61 into position 1, stack is 36-25-61 IL_000e: xor //stack is 36-36 IL_000f: dup //stack is 36-36-36 IL_0010: stloc.0 //store 36 into positon 0, stack is 36-36 IL_0011: xor //stack is 0, as the original 36 (instead of the new 61) is xor-ed) IL_0012: stloc.1 //store 0 into position 1 

Il est évident que le code généré dans la deuxième méthode est incohérent, car l’ancienne valeur de j est utilisée dans un calcul où la nouvelle valeur est requirejse.

C # charge j , i , j , i sur la stack et stocke chaque résultat XOR sans mettre à jour la stack, de sorte que le XOR le plus à gauche utilise la valeur initiale pour j .

Réécriture:

 j ^= i; i ^= j; j ^= i; 

En expansion ^= :

 j = j ^ i; i = j ^ i; j = j ^ i; 

Remplacer:

 j = j ^ i; j = j ^ (i = j ^ i); 

Remplacer cela ne fonctionne que si / parce que le côté gauche de l’opérateur ^ est évalué en premier:

 j = (j = j ^ i) ^ (i = i ^ j); 

Collapse ^ :

 j = (j ^= i) ^ (i ^= j); 

Symésortingquement:

 i = (i ^= j) ^ (j ^= i);