为什么使用这种复合forms时,用XOR交换值失败?

我发现这个代码交换两个数字,而不使用第三个variables,使用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 

现在,我想知道, 为什么我的代码提供不正确的输出?

编辑:好的,明白了。

第一点是显然你不应该使用这个代码。 但是,当您展开它时,它将等同于:

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

(如果我们使用一个更复杂的expression式,例如foo.bar++ ^= i ,那么只需要对++进行一次评估就很重要,但是在这里我相信它更简单。)

现在,操作数的评估顺序总是从左到右,所以首先我们得到:

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

这(上面)是最重要的一步。 最后执行的XOR操作的LHS最终为36。 LHS不是“RHS评估后j的价值”。

^的RHS评价涉及“一级嵌套”expression式,因此它变成:

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

然后看最深层次的嵌套,我们可以replaceij

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

…变成

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

在RHS中对j的赋值首先发生,但是结果会在最后被覆盖,所以我们可以忽略 – 在最后的赋值之前没有对j进一步评估:

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

这现在相当于:

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

要么:

 i = 36; j = 36 ^ 36; 

这成为:

 i = 36; j = 0; 

认为这一切都是正确的,并得到正确的答案…如果一些关于评估顺序的细节稍微closures,向Eric Lippert道歉:(

检查生成的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的旧值用于需要新值的计算中。

C#将jiji加载到堆栈上,并在不更新堆栈的情况下存储每个XOR结果,因此最左边的XOR使用j的初始值。