Language agnostic XOR变量交换是如何工作的?

Language agnostic XOR变量交换是如何工作的?,language-agnostic,bit-manipulation,xor,Language Agnostic,Bit Manipulation,Xor,有人能给我解释一下没有临时变量的两个变量的异或交换是如何工作的吗 void xorSwap (int *x, int *y) { if (x != y) { *x ^= *y; *y ^= *x; *x ^= *y; } } 我理解它的功能,但有人能告诉我它是如何工作的吗 大多数人会使用临时变量交换两个变量x和y,如下所示: tmp = x x = y y = tmp // A,B = 1,2 A = A+B // 3,2

有人能给我解释一下没有临时变量的两个变量的异或交换是如何工作的吗

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

我理解它的功能,但有人能告诉我它是如何工作的吗

大多数人会使用临时变量交换两个变量x和y,如下所示:

tmp = x
x = y
y = tmp
 // A,B  = 1,2
A = A+B // 3,2
B = A-B // 3,1
A = A-B // 2,1
这里有一个简单的编程技巧,可以在不需要临时变量的情况下交换两个值:

x = x xor y
y = x xor y
x = x xor y
详情请参阅

在第1行中,我们将x和y组合(使用XOR)得到这个“混合体”,并将其存储回x中。XOR是保存信息的好方法,因为您可以通过再次执行XOR来删除信息

第2行。我们将混合体与y进行异或运算,这将抵消所有y信息,只剩下x。我们将此结果保存回y,因此现在它们已交换

在最后一行中,x仍然具有混合值。我们再次使用y(现在是x的原始值)对其进行异或,以从混合体中删除x的所有痕迹。这就剩下y了,交换完成了


计算机实际上有一个隐式的“temp”变量,它在将中间结果写回寄存器之前存储中间结果。例如,如果将3添加到寄存器(在机器语言伪代码中):

ALU(算术逻辑单元)实际上执行指令3+A。它接受输入(3,A)并创建结果(3+A),然后CPU将其存储回A的原始寄存器。所以,在我们得到最终答案之前,我们使用ALU作为临时的暂存空间

我们认为ALU的隐式临时数据是理所当然的,但它总是存在的。以类似的方式,在x=x XOR y的情况下,ALU可以返回XOR的中间结果,此时CPU将其存储到x的原始寄存器中

因为我们不习惯考虑可怜的、被忽视的ALU,所以XOR交换看起来很神奇,因为它没有显式的临时变量。有些机器有一个1步交换XCHG指令来交换两个寄存器


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

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,x xor x==0

y2 = x0 xor 0
x2 = 0 xor 0 xor y0
y2 = x0
x2 = y0
对于任何x,由于
xor 0==x

y2 = x0 xor 0
x2 = 0 xor 0 xor y0
y2 = x0
x2 = y0

交换完成了。

这里有一个应该更容易找到的:

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 
,因此:


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

早在我们有简单的单周期或多周期CPU的时候,使用这个技巧来避免代价高昂的内存解引用或寄存器溢出到堆栈是比较便宜的。然而,我们现在有了带有大量管道的CPU。P4的流水线从20级到31级不等,在这些流水线中,对寄存器的读写之间的任何依赖都可能导致整个进程停滞。xor交换在A和B之间有一些非常严重的依赖关系,这些依赖关系实际上并不重要,但在实践中会使管道停滞。停滞的管道会导致代码路径变慢,如果这个交换在您的内部循环中,那么您的移动将非常缓慢

在一般实践中,当您使用temp变量进行交换时,编译器可以确定您真正想要做什么,并可以将其编译为单个XCHG指令。使用xor交换使编译器更难猜测您的意图,因此不太可能正确地优化它。更不用说代码维护等了。

没错,这是一个巧妙的数学技巧。想象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

XOR方法基本上有3个步骤:

a'=a异或b(1)
b'=a'XOR b(2)
a“=a‘异或b’(3)

要理解为什么这样做,首先请注意:

  • 只有当XOR的一个操作数为1,另一个操作数为零时,XOR才会产生1
  • XOR是可交换的,所以a XOR b=b XOR a
  • XOR是联想的so(a XOR b)XOR c=a XOR(b XOR c);及
  • XOR a=0(从上面的定义可以明显看出)
  • 在步骤(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是关联的
    =由于上述[4]而产生的异或0
    =由于XOR的定义(见上文)

    现在我们可以替换为步骤(3):

    a“=(a或b)或a
    =(b XOR a)XOR a,因为XOR是可交换的
    =b XOR(a XOR a),因为XOR是关联的
    =b XOR 0,因为上面[4]的原因
    =b由于XOR的定义(见上文)

    更多详细信息请参见:

    它之所以有效,是因为XOR不会丢失信息。如果可以忽略溢出,则可以对普通的加减法执行相同的操作。例如,如果变量对A、B最初包含值1、2,则可以这样交换它们:

    tmp = x
    x = y
    y = tmp
    
     // 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,依此类推。倒过来也行。要沿着列表运行,需要保留指向两个连续块的指针。当然,您将使用XOR代替加法/子运算,因此您不必担心溢出

    如果你想把它扩展到一个“链接的网络”
    = a ^ b ^ a 
    = a ^ a ^ b 
    = 0 ^ b 
    = b
    
           x: |1|0|1|1|   -> 8 + 2 + 1
           y: |0|1|0|1|   -> 4 + 1
    
         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!
    
    a = a XOR b
    b = a XOR b
    a = a XOR 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 = 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 = 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)
    
    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
    
    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
    
    A = 1100
    B = 0101
    
    
    A ^= B;     => A = 1100 XOR 0101
    (A = 1001)
    
    B ^= A;     => B = 0101 XOR 1001
    (B = 1100)
    
    A ^= B;     => A = 1001 XOR 1100
    (A = 0101)
    
    
    A = 0101
    B = 1100