XOR temp変数のない2つの変数の交換がどのように機能するかを誰かに説明できますか?
void xorSwap (int *x, int *y)
{
if (x != y) {
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
}
私はそれが何をしているのか理解していますが、誰かがそれがどのように機能するかの論理を私に説明することができますか?
置換を行うことで、それがどのように機能するかを確認できます。
x1 = x0 xor y0
y2 = x1 xor y0
x2 = x1 xor y2
置換、
x1 = x0 xor y0
y2 = (x0 xor y0) xor y0
x2 = (x0 xor y0) xor ((x0 xor y0) xor y0)
Xorは完全に結合的かつ可換的であるため:
y2 = x0 xor (y0 xor y0)
x2 = (x0 xor x0) xor (y0 xor y0) xor y0
x xor x == 0
任意のx、
y2 = x0 xor 0
x2 = 0 xor 0 xor y0
それ以来 x xor 0 == x
任意のx、
y2 = x0
x2 = y0
そして、スワップが行われます。
他の人々がそれを説明してくれました、今私はそれがなぜ良い考えだったのかを説明したいのですが、今はそうではありません。
単純なシングルサイクルまたはマルチサイクルのCPUがあった当時、このトリックを使用すると、コストのかかるメモリの逆参照やスタックへのレジスターの流出を避けることができました。ただし、代わりに大規模なパイプラインを備えたCPUがあります。 P4のパイプラインは、パイプラインに20〜31(またはそれ以上)のステージがあり、レジスタの読み取りと書き込みの間に依存関係があると、全体が停止する可能性があります。 xorスワップには、AとBの間の非常に重い依存関係がいくつかありますが、実際にはまったく関係ありませんが、実際にはパイプラインを停止します。パイプラインが停止するとコードパスが遅くなり、このループが内側のループにあると、動きが非常に遅くなります。
一般的に、コンパイラーは、一時変数を使用してスワップを行うときに実際に何をしたいかを理解し、それを単一のXCHG命令にコンパイルできます。 xor swapを使用すると、コンパイラーが意図を推測することがはるかに困難になるため、コンパイラーが正しく最適化する可能性が低くなります。コードのメンテナンスなどは言うまでもありません.
私はそれを数値ではなくグラフィカルに考えるのが好きです。
X = 11とy = 5で始めるとしましょう(バイナリでは、仮想の4ビットマシンを使用します)。ここにxとyがあります。
x: |1|0|1|1| -> 8 + 2 + 1
y: |0|1|0|1| -> 4 + 1
さて、私にとっては、XORは反転演算であり、2回実行するとミラーになります。
x^y: |1|1|1|0|
(x^y)^y: |1|0|1|1| <- ooh! Check it out - x came back
(x^y)^x: |0|1|0|1| <- ooh! y came back too!
ここでは、少し簡単に理解できるはずです。
int x = 10, y = 7;
y = x + y; //x = 10, y = 17
x = y - x; //x = 7, y = 17
y = y - x; //x = 7, y = 10
XORトリックは、^が+or-。次のように:
x + y - ((x + y) - x) == x
、 そう:
x ^ y ^ ((x ^ y) ^ x) == x
XORは情報を失わないためです。オーバーフローを無視できれば、通常の加算と減算で同じことができます。たとえば、変数ペアA、B最初は値1、2が含まれていますが、次のように入れ替えることができます。
// A,B = 1,2
A = A+B // 3,2
B = A-B // 3,1
A = A-B // 2,1
ところで、双方向のリンクされたリストを単一の「ポインタ」にエンコードするための古いトリックがあります。アドレスA、B、Cにメモリブロックのリストがあるとします。各ブロックの最初のワードはそれぞれです。
// first Word of each block is sum of addresses of prior and next block
0 + &B // first Word of block A
&A + &C // first Word of block B
&B + 0 // first Word of block C
ブロックAにアクセスできる場合は、Bのアドレスが表示されます。Cに到達するには、Bの「ポインター」を取得し、Aを減算します。逆方向にも同様に機能します。リストに沿って実行するには、2つの連続するブロックへのポインタを保持する必要があります。もちろん、追加/減算の代わりにXORを使用するので、オーバーフローを心配する必要はありません。
楽しみたい場合は、これを「リンクされたWeb」に拡張できます。
ほとんどの人は、次のように一時変数を使用して2つの変数xとyを交換します。
tmp = x
x = y
y = tmp
ここでは、tempを必要とせずに2つの値を交換するための適切なプログラミングトリックを示します。
x = x xor y
y = x xor y
x = x xor y
1行目では、xとyを(XORを使用して)結合してこの「ハイブリッド」を取得し、xに格納します。 XORは、XORをもう一度実行することで情報を削除できるため、情報を保存するための優れた方法です。
2行目では、XOR yとのハイブリッドです。すべてのy情報が取り消され、xだけが残ります。この結果をyに保存して、入れ替えました。
最後の行では、xはまだハイブリッド値を持っています。 XORもう一度y(今はxの元の値)を使用して、ハイブリッドからxのすべてのトレースを削除します。これにより、yが残り、スワップが完了します!
コンピュータには実際には、中間結果をレジスタに書き戻す前に格納する暗黙の「temp」変数があります。たとえば、(マシン言語の疑似コードで)レジスターに3を追加すると、次のようになります。
ADD 3 A // add 3 to register A
ALU(算術論理演算ユニット)は、実際には命令3 + Aを実行するものです。入力(3、A)を取り、結果(3 + A)を作成します。CPUはそれをAの元のレジスタに保存します。したがって、最終的な回答が得られる前に、ALUを一時的なスクラッチスペースとして使用しました。
ALUの暗黙の一時データは当然のことと考えていますが、常にそこにあります。同様に、ALUは、x = x xor yの場合、XORの中間結果を返すことができます。この時点で、CPUはxの元のレジスタに格納します。
貧弱な無視されたALUについて考えることに慣れていないため、XOR swapは、明示的な一時変数がないため、魔法のように見えます。一部のマシンでは、1ステップの交換XCHG命令があります2つのレジスタを交換します。
@ VonC それは正しい、それはきちんとした数学のトリックです。 4ビットの単語を想像して、これが役立つかどうかを確認してください。
Word1 ^= Word2;
Word2 ^= Word1;
Word1 ^= Word2;
Word1 Word2
0101 1111
after 1st xor
1010 1111
after 2nd xor
1010 0101
after 3rd xor
1111 0101
余談ですが、私はこのホイールを数年前に独立して整数を交換する形で再発明しました。
a = a + b
b = a - b ( = a + b - b once expanded)
a = a - b ( = a + b - a once expanded).
(これは読みにくい方法で上に述べられています)、
まったく同じ理由がxorスワップに適用されます:a ^ b ^ b = aおよびa ^ b ^ a = a。 xorは可換なので、x ^ x = 0およびx ^ 0 = xなので、これは非常に簡単に確認できます。
= a ^ b ^ b
= a ^ 0
= a
そして
= a ^ b ^ a
= a ^ a ^ b
= 0 ^ b
= b
お役に立てれば。この説明はすでに与えられています...しかし、いちいちはっきりとは言えません。