Perché lo scambio di valori con XOR fallisce quando si utilizza questa forma composta?

Ho trovato questo codice per scambiare due numeri senza usare una terza variabile, usando l’operatore XOR ^ .

Codice:

 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 

Ora ho cambiato il codice precedente con questo codice equivalente.

Il mio codice:

 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 

Ora, voglio sapere, perché il mio codice fornisce risultati errati?

EDIT: Ok, capito.

Il primo punto da fare è che ovviamente non dovresti usare comunque questo codice. Tuttavia, quando lo espandi, diventa equivalente a:

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

(Se foo.bar++ ^= i un’espressione più complicata come foo.bar++ ^= i , sarebbe importante che il ++ fosse valutato solo una volta, ma qui credo sia più semplice.)

Ora, l’ordine di valutazione degli operandi è sempre da sinistra a destra, quindi per iniziare otteniamo:

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

Questo (sopra) è il passo più importante. Abbiamo finito con 36 come LHS per l’operazione XOR che viene eseguita per ultima. Il LHS non è “il valore di j dopo che l’RHS è stato valutato”.

La valutazione del RHS del ^ implica l’espressione “un livello annidato”, quindi diventa:

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

Quindi, osservando il livello più profondo di nidificazione, possiamo sostituire entrambi i e j :

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

… che diventa

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

L’assegnazione a j nell’RHS avviene prima, ma il risultato viene comunque sovrascritto alla fine, quindi possiamo ignorarlo – non ci sono ulteriori valutazioni di j prima dell’assegnazione finale:

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

Questo è ora equivalente a:

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

O:

 i = 36; j = 36 ^ 36; 

Che diventa:

 i = 36; j = 0; 

Penso che sia tutto corretto, e arriva alla risposta giusta … scusa Eric Lippert se alcuni dettagli sull’ordine di valutazione sono leggermente off 🙁

Controllato l’IL generato e dà risultati diversi;

Lo scambio corretto genera un semplice:

 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] 

Lo scambio errato genera questo codice:

 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 

È evidente che il codice generato nel secondo metodo è incorpecifico, poiché il vecchio valore di j viene utilizzato in un calcolo in cui è richiesto il nuovo valore.

C # carica j , i , j , i nello stack e memorizza ogni risultato XOR senza aggiornare lo stack, quindi il XOR più a sinistra utilizza il valore iniziale per j .

riscrittura:

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

Espansione ^= :

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

Sostituire:

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

Sostituire questo funziona solo se / perché la parte sinistra dell’operatore ^ viene valutata per prima:

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

Comprimi ^ :

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

Simmetricamente:

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