web-dev-qa-db-ja.com

この複合形式を使用すると、XORで値を交換できないのはなぜですか?

XOR ^演算子を使用して、3番目の変数を使用せずに2つの数値を交換するこのコードを見つけました。

コード:

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

今、私は知りたいです、なぜ私のコードは間違った出力を与えるのですか?

76
Javed Akram

編集:わかりました、わかりました。

最初のポイントは、明らかにこのコードを使用すべきではないということです。ただし、展開すると、次のようになります。

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

foo.bar++ ^= iなどのより複雑な式を使用している場合は、++を1回だけ評価することが重要ですが、ここではもっと簡単だと思います。)

これで、オペランドの評価の順序は常に左から右になるため、最初に次のようになります。

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

これ(上記)が最も重要なステップです。最後に実行されるXOR操作のLHSとして36になりました。LHSは "ではありません。 RHSが評価された後のjの値」。

^のRHSの評価には、「1レベルのネストされた」式が含まれるため、次のようになります。

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

次に、ネストの最も深いレベルを見ると、ijの両方を置き換えることができます。

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;

thinkはすべて正しいと思います、そしてそれは正しい答えになります...評価順序に関する詳細のいくつかがわずかにずれている場合はEric Lippertに謝罪します: ((

76
Jon Skeet

生成された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の古い値が新しい値が必要な計算で使用されるため、2番目の方法で生成されたコードが正しくないことは明らかです。

15
SWeko

C#はjijiをスタックにロードし、スタックを更新せずに各XORの結果を格納するため、左端のXORは、jの初期値を使用します。

7
C.Evenhuis

書き換え:

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);
0
Wouter