77 votes

Pourquoi l'échange de valeurs avec XOR échoue-t-il lorsqu'on utilise cette forme composée ?

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

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 par 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 un résultat incorrect ?

2 votes

Ce serait la même réponse que celle donnée pour : stackoverflow.com/questions/3741440 ou beaucoup d'autres dans la colonne connexe. Même s'ils disent C++ et qu'il s'agit de C#, les mêmes règles s'appliquent.

8 votes

@Daemin : Non, les mêmes règles Ne le fais pas. appliquer. C'est un comportement indéfini en C++, mais je ne crois pas qu'il soit indéfini en C#.

3 votes

@Daemin - Voici un billet connexe d'Eric Lippert, il y a tout juste deux jours : stackoverflow.com/questions/5538193/ en particulier : "Les autres réponses soulignent que dans les langages de programmation C et C++, les spécifications du langage ne précisent pas dans quel ordre les effets secondaires semblent se produire si l'effet secondaire et son observation se trouvent dans le même "point de séquence", comme c'est le cas ici. [...] Le C# ne permet pas ce genre de lattitude. En C#, un effet secondaire à gauche est observé comme s'étant produit au moment où le code à droite s'exécute."

78voto

Jon Skeet Points 692016

EDIT : Ok, je l'ai.

La première chose à dire est qu'il est évident que vous ne devriez 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 utilisions une expression plus compliquée telle que foo.bar++ ^= i il serait important que l ++ n'a été évalué qu'une seule fois, mais ici je crois que c'est plus simple).

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

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

Cette étape (ci-dessus) est la plus importante. Nous nous sommes retrouvés avec 36 comme LHS pour l'opération XOR qui est exécutée en dernier. La LHS n'est pas "la valeur de j après l'évaluation de l'ERS".

L'évaluation du RHS de ^ implique l'expression "un niveau emboîté", elle devient donc :

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

Ensuite, en regardant le niveau le plus profond de l'imbrication, nous pouvons 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 de toute façon écrasé à la fin, donc nous pouvons l'ignorer - il n'y a pas d'autres évaluations de j avant l'affectation finale :

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

C'est maintenant équivalent à :

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

Ou :

i = 36;
j = 36 ^ 36;

Ce qui devient :

i = 36;
j = 0;

I pensez à c'est correct, et cela mène à la bonne réponse... mes excuses à Eric Lippert si certains détails concernant l'ordre d'évaluation sont légèrement erronés :(

0 votes

@Jon Skeet : Merci, maintenant c'est clair pour moi. +100 Excellente réponse :)

0 votes

Donc, en conclusion, ça va marcher : j = (i ^= j ^= i) ^ j; qui est à peu près le même, sans la partie la plus à gauche. j .

1 votes

La VA suggère que c'est exactement ce qui se passe.

15voto

SWeko Points 17524

J'ai vérifié l'IL généré et il donne des résultats différents ;

L'échange correct génère un direct :

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]

L'échange 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é par la deuxième méthode est incorect, car l'ancienne valeur de j est utilisée dans un calcul où la nouvelle valeur est requise.

0 votes

J'ai vérifié la sortie et cela donne des résultats différents :) . La question est de savoir pourquoi cela se produit...

0 votes

Il charge donc d'abord toutes les valeurs dont il a besoin pour évaluer l'expression entière sur la pile, puis il xore et sauvegarde les valeurs dans les variables au fur et à mesure (il utilisera donc les valeurs initiales de i et j tout au long de l'évaluation de l'expression).

0 votes

Ajout d'une explication pour le deuxième IL

7voto

C.Evenhuis Points 10818

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

0voto

Wouter Points 773

Réécriture :

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

Élargissement du site ^= :

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

Remplaçant :

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

Substitute this 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étriquement :

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

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