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
今、私は知りたいです、なぜ私のコードは間違った出力を与えるのですか?
編集:わかりました、わかりました。
最初のポイントは、明らかにこのコードを使用すべきではないということです。ただし、展開すると、次のようになります。
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));
次に、ネストの最も深いレベルを見ると、i
とj
の両方を置き換えることができます。
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に謝罪します: ((
生成された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番目の方法で生成されたコードが正しくないことは明らかです。
C#はj
、i
、j
、i
をスタックにロードし、スタックを更新せずに各XOR
の結果を格納するため、左端のXOR
は、j
の初期値を使用します。
書き換え:
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);