有人可以向我解释两个没有临时变量的XOR交换如何工作吗?
void xorSwap (int *x, int *y)
{
if (x != y) {
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
}
我了解它的作用,但是有人可以引导我了解它的工作原理吗?
Answers:
您可以通过执行替换来查看其工作原理:
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
交换完成。
y2
为的冲动y1
。它触发了我,您拥有x0
andx1
然后使用y0
and y2
。:-]
其他人已经解释了它,现在我想解释为什么这是一个好主意,但现在不是。
早在我们拥有简单的单周期或多周期CPU的那一天,使用这种技巧来避免昂贵的内存取消引用或将寄存器溢出到堆栈中的成本就比较便宜。但是,现在我们有了带有大量流水线的CPU。P4的流水线在其流水线中具有20到31个(左右)级,其中对寄存器的读写之间的任何依赖关系都可能导致整个过程停滞。Xor交换在A和B之间有一些非常严重的依赖关系,这些依赖关系实际上并不重要,但实际上会使管道停滞不前。停滞的管道会导致代码路径变慢,并且如果此交换位于您的内部循环中,那么您的移动将非常缓慢。
通常,编译器可以在执行与temp变量的交换时弄清楚您真正想要做什么,并将其编译为单个XCHG指令。使用xor交换使编译器更难猜测您的意图,因此不太可能正确优化它。更不用说代码维护等。
我喜欢以图形方式而不是数字方式考虑它。
假设您以x = 11和y = 5开头(在二进制文件中(我将使用一个假设的4位计算机),这里是x和y
x: |1|0|1|1| -> 8 + 2 + 1
y: |0|1|0|1| -> 4 + 1
现在对我来说,XOR是一个反转操作,两次执行都是一个镜像:
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技巧。正如:
x + y - ((x + y) - x) == x
,因此:
x ^ y ^ ((x ^ y) ^ x) == x
大多数人会使用一个临时变量交换两个变量x和y,如下所示:
tmp = x
x = y
y = tmp
这是一个巧妙的编程技巧,可以在不使用临时文件的情况下交换两个值:
x = x xor y
y = x xor y
x = x xor y
使用XOR交换两个变量的更多详细信息
在第1行上,我们将x和y组合(使用XOR)以获得此“混合”,然后将其存储回x中。XOR是保存信息的好方法,因为您可以通过再次执行XOR来删除它。
在第2行上,我们将y与x混合,从而抵消了所有y信息,只剩下x。我们将此结果保存回y,所以现在它们已互换。
在最后一行,x仍然具有混合值。我们再次将其与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的隐式临时数据是理所当然的,但它始终存在。以类似的方式,在x = x xor y的情况下,ALU可以返回XOR的中间结果,此时CPU将其存储到x的原始寄存器中。
因为我们不习惯考虑可怜的,被忽略的ALU,所以XOR交换似乎很神奇,因为它没有显式的临时变量。一些机器有一个1步交换XCHG指令来交换两个寄存器。
之所以起作用,是因为XOR不会丢失信息。如果您可以忽略溢出,则可以使用普通的加法和减法执行相同的操作。例如,如果变量对A,B最初包含值1,2,则可以像这样交换它们:
// A,B = 1,2
A = A+B // 3,2
B = A-B // 3,1
A = A-B // 2,1
顺便说一句,有一个古老的技巧可以在单个“指针”中编码2向链表。假设您有一个位于地址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,依此类推。向后效果也一样。要沿列表运行,您需要保持指向两个连续块的指针。当然,您可以使用XOR代替加法/减法,因此您不必担心溢出。
如果您想获得一些乐趣,可以将其扩展到“链接的网站”。
int
溢出是UB)
异或方法基本上包括3个步骤:
a'= a XOR b(1)
b'= a'XOR b(2)
a” = a'XOR b'(3)
要了解为什么这样做有效,首先请注意:
在步骤(1)之后,a的二进制表示形式仅在a和b具有相反位的位位置中才具有1位。即(ak = 1,bk = 0)或(ak = 0,bk = 1)。现在,当我们在步骤(2)中进行替换时,我们得到:
b'=(a XOR b)XOR b
= a XOR(b XOR b),因为XOR是关联的
= a XOR 0,因为上面的[4]
= a是由于XOR的定义(请参见上面的1)
现在我们可以代入步骤(3):
a” =(a XOR b)XOR a
=(b XOR a)XOR a因为XOR是可交换的
= b XOR(a XOR a)因为XOR是关联
= b XOR 0是因为上面的[4]
= b由于定义XOR(请参阅上面的1)
此处提供更多详细信息: 必要且足够
作为附带说明,几年前,我通过交换整数的方式独立地重新发明了该轮子,方法是:
a = a + b
b = a - b ( = a + b - b once expanded)
a = a - b ( = a + b - a once expanded).
(以上提到的内容很难理解),
完全相同的推理也适用于异或交换: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
希望这可以帮助。已经给出了这种解释……但不是很清楚。
我只想添加一个数学解释以使答案更加完整。在群论中,XOR是一个阿贝尔群,也称为可交换群。这意味着它满足五个要求:闭包,关联性,标识元素,逆元素,可交换性。
XOR交换公式:
a = a XOR b
b = a XOR b
a = a XOR b
展开公式,将a,b替换为以前的公式:
a = a XOR b
b = a XOR b = (a XOR b) XOR b
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
可交换性是指“ a XOR b”等于“ b XOR a”:
a = a XOR b
b = a XOR b = (a XOR b) XOR b
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
关联性是指“(a XOR b)XOR c”等于“ a XOR(b XOR c)”:
a = a XOR b
b = a XOR b = (a XOR b) XOR b
= a XOR (b XOR b)
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
= b XOR (a XOR a) XOR (b XOR b)
XOR中的逆元素本身就是它,这意味着任何XOR本身的值都为零:
a = a XOR b
b = a XOR b = (a XOR b) XOR b
= a XOR (b XOR b)
= a XOR 0
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
= b XOR (a XOR a) XOR (b XOR b)
= b XOR 0 XOR 0
XOR中的标识元素为零,这意味着任何具有零的XOR值均保持不变:
a = a XOR b
b = a XOR b = (a XOR b) XOR b
= a XOR (b XOR b)
= a XOR 0
= a
a = a XOR b = (a XOR b) XOR (a XOR b) XOR b
= (b XOR a) XOR (a XOR b) XOR b
= b XOR (a XOR a) XOR (b XOR b)
= b XOR 0 XOR 0
= b XOR 0
= b
并且您可以获得团体理论方面的更多信息。