Почему при использовании этой составной формы обмен значениями с XOR не выполняется? - PullRequest
76 голосов
/ 07 апреля 2011

Я нашел этот код для замены двух чисел без использования третьей переменной, используя оператор XOR ^.

Код:

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

Теперь яизменил приведенный выше код на этот эквивалентный код.

Мой код:

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

Теперь я хочу знать, Почему мой код дает неправильный вывод

Ответы [ 4 ]

76 голосов
/ 07 апреля 2011

РЕДАКТИРОВАТЬ: Хорошо, понял.

Первое, на что нужно обратить внимание, это то, что вы, очевидно, не должны использовать этот код в любом случае. Однако, когда вы расширяете его, оно становится эквивалентным:

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

(Если бы мы использовали более сложное выражение, такое как foo.bar++ ^= i, было бы важно, чтобы ++ оценивалось только один раз, но здесь я считаю, что это проще.)

Теперь порядок вычисления операндов всегда слева направо, поэтому для начала получим:

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

Это (выше) самый важный шаг. В итоге мы получили 36 в качестве LHS для операции XOR, которая выполняется последней. LHS не является «значением j после оценки RHS».

Оценка RHS для ^ включает в себя «одноуровневое» выражение, поэтому оно становится:

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

Затем, глядя на самый глубокий уровень вложенности, мы можем заменить i и j:

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

... который становится

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

Первоначально присваивается j в RHS, но результат все равно перезаписывается в конце, поэтому мы можем игнорировать это - дальнейших оценок j до окончательного присвоения нет:

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

Теперь это эквивалентно:

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

Или:

i = 36;
j = 36 ^ 36;

Что становится:

i = 36;
j = 0;

Я думаю это все правильно, и он получает правильный ответ ... извинения Эрику Липперту, если некоторые детали о порядке оценки немного не соответствуют: (

15 голосов
/ 07 апреля 2011

Проверил сгенерированный IL и выдает разные результаты;

Правильный своп генерирует прямую:

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]

Неправильный своп генерирует этот код:

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

Очевидно, что код, сгенерированный во втором методе, является неверным, поскольку старое значение j используется в вычислениях, где требуется новое значение.

7 голосов
/ 07 апреля 2011

C # загружает j, i, j, i в стек и сохраняет каждый результат XOR без обновления стека, поэтому крайний левый XOR использует начальное значение для j .

0 голосов
/ 21 ноября 2017

Перезапись:

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

Расширение ^=:

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

Замена:

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

Заменить это работает, только если / потому что левая часть оператора ^ вычисляется первой:

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

Свернуть ^:

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

Симметрично:

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