XOR变量交换如何工作?


76

有人可以向我解释两个没有临时变量的XOR交换如何工作吗?

void xorSwap (int *x, int *y)
{
    if (x != y) {
        *x ^= *y;
        *y ^= *x;
        *x ^= *y;
    }
}

我了解它的作用,但是有人可以引导我了解它的工作原理吗?


6
我认为xor变量swap在乱序的执行核心上很烂。每个后续的xor具有写后读取的依赖性,并且需要等待答案完成。对于x86,最好还是照常进行编码。编译器应该发出一些体面的东西。
2009年

Answers:


130

您可以通过执行替换来查看其工作原理:

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

交换完成。


如果您11年后再看到此评论,我没有任何想法,但这是有史以来最好的解释,谢谢!
Cantaff0rd

接近12年后:这如何处理字符串(如字符串反转)?是不是因为您不是对ASCII值进行运算,而是对包含字符串各个部分的内存地址的二进制表示进行运算?
bluppfisk

我几乎无法忍受改变y2为的冲动y1。它触发了我,您拥有x0andx1然后使用y0and y2。:-]
Frerich Raabe

94

其他人已经解释了它,现在我想解释为什么这是一个好主意,但现在不是。

早在我们拥有简单的单周期或多周期CPU的那一天,使用这种技巧来避免昂贵的内存取消引用或将寄存器溢出到堆栈中的成本就比较便宜。但是,现在我们有了带有大量流水线的CPU。P4的流水线在其流水线中具有20到31个(左右)级,其中对寄存器的读写之间的任何依赖关系都可能导致整个过程停滞。Xor交换在A和B之间有一些非常严重的依赖关系,这些依赖关系实际上并不重要,但实际上会使管道停滞不前。停滞的管道会导致代码路径变慢,并且如果此交换位于您的内部循环中,那么您的移动将非常缓慢。

通常,编译器可以在执行与temp变量的交换时弄清楚您真正想要做什么,并将其编译为单个XCHG指令。使用xor交换使编译器更难猜测您的意图,因此不太可能正确优化它。更不用说代码维护等。


是的-与所有节省内存的技巧一样,在如今便宜的内存中,它并不是那么有用。
布鲁斯·奥尔德曼

1
同样,嵌入式系统cpus仍然受益匪浅。
保罗·内森

@Paul,这取决于您的工具链。我将首先对其进行测试,以确保您的嵌入式编译器尚未执行该优化。
Patrick

1
(同样值得注意的是,从大小的角度来看,三个XOR可能大于一个XCHG,具体取决于架构。您可以通过不使用xor技巧来节省更多空间。)
Patrick

53

我喜欢以图形方式而不是数字方式考虑它。

假设您以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!

非常清楚。在每个位上执行每个XOR操作可以更轻松地了解发生了什么。我认为更难理解XOR,因为与&和|不同 操作,这在您的头脑中要困难得多。XOR算法只会导致混乱。不要害怕将问题可视化,编译器在那里进行数学运算,而不是您。
马丁·舒特

35

这是应该更容易理解的一种:

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

@Matt J,感谢您的减法示例。它确实帮了我忙。
mmcdole

3
可能值得强调的是,由于大量溢出,您不能使用加法或减法。
MarkJ

是这样吗 在我计算出的小示例中,无论如何(假设下溢或上溢的结果为(结果%2 ^ n)),一切正常。我可能会编写一些代码进行测试。
Matt J

我认为,假设ADD和SUB指令采用最简化的硬件实现,即使在存在上溢或下溢的情况下,此方法也能正常工作。我刚刚测试过。我想念什么吗?
Matt J

我想,如果您没有溢出和下溢的例外,那肯定可以。
MarkJ

13

大多数人会使用一个临时变量交换两个变量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指令来交换两个寄存器。


4
我知道,我在问它是如何工作的。使用互斥或​​值如何使您可以在没有临时变量的情况下交换它
mmcdole

赞成,因为这是最清晰,最详细的答案,但要注意的是,使用temp变量进行的交换更具可读性,并因此而在代码中具有更大的价值
眼睑失明

1
我喜欢原始答案(带有解释),但是关于ALU的内容似乎是误导的。即使在您提到的单周期(非流水线)处理器上,在1条指令中执行“ x =(运算涉及x)”的能力也与寄存器文件具有输入输出端口这一事实有关。
Matt J

13

之所以起作用,是因为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代替加法/减法,因此您不必担心溢出。

如果您想获得一些乐趣,可以将其扩展到“链接的网站”。


单指针技巧非常棒,不知道这一点!谢谢!
加布·罗耶

1
@Gab:不客气,您的英语水平比我的法语好很多!
Mike Dunlavey

1
对于+/-方法+1(尽管int溢出是UB)
chux-恢复莫妮卡

7

@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

5

异或方法基本上包括3个步骤:

a'= a XOR b(1)
b'= a'XOR b(2)
a” = a'XOR b'(3)

要了解为什么这样做有效,首先请注意:

  1. 仅当XOR的一个操作数正好为1,另一个为零时,XOR才会产生1。
  2. XOR是可交换的,因此XOR b = b XOR a;
  3. XOR是关联的,所以(a XOR b)XOR c = a XOR(b XOR c);和
  4. a XOR a = 0(根据上面1中的定义,这应该很明显)

在步骤(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

此处提供更多详细信息: 必要且足够


3

作为附带说明,几年前,我通过交换整数的方式独立地重新发明了该轮子,方法是:

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

希望这可以帮助。已经给出了这种解释……但不是很清楚。


3

我只想添加一个数学解释以使答案更加完整。在群论中,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

并且您可以获得团体理论方面的更多信息。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.