Pertanyaan Mengapa bertukar nilai dengan XOR gagal saat menggunakan formulir senyawa ini?


Saya menemukan kode ini untuk menukar dua angka tanpa menggunakan variabel ketiga, menggunakan XOR ^ operator.

Kode: 

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

Sekarang saya mengubah kode di atas ke kode yang setara ini.

Kode Saya: 

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

Sekarang, saya ingin tahu, Mengapa kode saya memberikan hasil yang salah?


76
2018-04-07 06:56


asal


Jawaban:


EDIT: Oke, mengerti.

Hal pertama yang harus dilakukan adalah bahwa jelas Anda tidak harus menggunakan kode ini. Namun, ketika Anda mengembangkannya, itu menjadi setara dengan:

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

(Jika kami menggunakan ekspresi yang lebih rumit seperti foo.bar++ ^= i, itu akan menjadi penting bahwa ++ hanya dievaluasi sekali, tapi di sini saya percaya itu lebih sederhana.)

Sekarang, urutan evaluasi operan selalu kiri ke kanan, jadi untuk memulai dengan kita mendapatkan:

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

Ini (di atas) adalah langkah yang paling penting. Kami telah berakhir dengan 36 sebagai LHS untuk operasi XOR yang dieksekusi terakhir. LHS bukan "nilai dari j setelah RHS dievaluasi ".

Evaluasi RHS dari ^ melibatkan "satu tingkat bersarang" ekspresi, sehingga menjadi:

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

Kemudian melihat pada tingkat nesting yang paling dalam, kita dapat mengganti keduanya i dan j:

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

... yang menjadi

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

Tugas untuk j di RHS terjadi pertama, tetapi hasilnya kemudian ditimpa pada akhirnya, jadi kita dapat mengabaikan itu - tidak ada evaluasi lebih lanjut dari j sebelum tugas akhir:

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

Ini sekarang setara dengan:

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

Atau:

i = 36;
j = 36 ^ 36;

Yang menjadi:

i = 36;
j = 0;

saya berpikir itu semua benar, dan itu sampai pada jawaban yang benar ... permintaan maaf kepada Eric Lippert jika beberapa rincian tentang urutan evaluasi sedikit :(


76
2018-04-07 07:07



Memeriksa IL yang dihasilkan dan memberikan hasil yang berbeda;

Tukar yang benar menghasilkan sesuatu yang langsung:

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]

Swap yang salah menghasilkan kode ini:

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

Jelaslah bahwa kode yang dihasilkan dalam metode kedua adalah incorect, karena nilai lama j digunakan dalam perhitungan di mana nilai baru diperlukan.


15
2018-04-07 07:18



C # memuat j, i, j, i di tumpukan, dan menyimpan masing-masing XOR hasil tanpa memperbarui tumpukan, jadi paling kiri XOR menggunakan nilai awal untuk j.


7
2018-04-07 07:22



Menulis ulang:

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

Memperluas ^=:

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

Pengganti:

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

Pengganti ini hanya berfungsi jika / karena sisi kiri operator ^ dievaluasi terlebih dahulu:

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

Jatuh ^:

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

Simetris:

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

0
2017-11-21 18:29