【问题标题】:Analysis of a "technique" for swapping 2 variables without a third temp variable [duplicate]分析在没有第三个临时变量的情况下交换 2 个变量的“技术”[重复]
【发布时间】:2015-10-18 23:40:20
【问题描述】:

我遇到了一种“技术”,用于交换 2 个变量(整数、字符或指针)没有第三个临时变量,如下所示:

int a = Something ;  
int b = SomethingElse ;  
a ^= b ;  
b ^= a ;  
a ^= b ;

正常的方式是:

int a = Something ;  
int b = SomethingElse ;  
int temp = a ;  
a = b ;  
b = temp ;

这一切都很好,但分享这种“技术”的人通常将其声明为不使用额外空间

(A) 没有多余的空间是真的吗?我认为,与“内存到内存 XOR 操作”相比,“内存到内存复制”需要更少的指令(机器代码)。

int temp = a <==> move content of memory location a to memory location temp **(1 instruction)**, possibly via a register **(2 instructions)**

a ^= b <==> move contents of memory location a to register1, move contents of memory location b to register2, xor register1 and register2 (store results in register1) , move register1 to memory location a **(about 4 instructions)**

似乎“技术”会导致更长的代码和更长的运行时间。

(B)“技术”在某些方面或某些情况下是否更快(或更好)?

似乎“技术”速度较慢,使用更多内存,并且不太适合浮点数。

编辑
似乎可能有一些潜在的重复:
Why don't people use xor swaps?

但是这个问题明显不同
(A) 该问题被关闭为“非建设性”,它“可能会引发辩论、争论、民意调查或扩展讨论”,而该问题正在寻找事实参考,例如“某事是真的吗?” &“这样更好吗?”
(B) 这个问题是关于为什么人们不使用“技术”,而这个问题是关于“技术”的分析,没有研究人们为什么使用还是不使用。

【问题讨论】:

  • @Prem 他们这样做是因为您还没有阅读“如何提问”,所以您会知道这不属于超级用户。 StackOverflow != 超级用户。
  • 确实XOR交换通常没有意义,而且在机器代码中临时变量通常只是一个寄存器,所以它基本上是免费的。此外,大多数架构不能对内存进行 XOR,并且无论如何都必须将至少一个操作数加载到寄存器中。如果两者恰好都在寄存器中,则交换基本上是无操作的,只是编译器重新分配寄存器。
  • 总而言之,这取决于CPU架构和编译器优化。通过编译器运行这两个变体,并比较输出。基本上能说的就这些了。至于代码的清晰度,实际上我更喜欢临时的。
  • 无用的招数在更无用的求职面试官中流行。
  • 根据this answer,编译器应该优化您的代码,因此XOR 没有任何改进。 this page 展示了如何使用这种技术和指针来交换任何类型的变量。

标签: c assembly


【解决方案1】:

没有明确的答案:这太依赖于:

  1. 机器的架构(处理器数量、内存缓存大小等);和
  2. 编译器优化的质量。

如果所有操作都在寄存器中执行,那么与复制相比,XOR 不太可能造成性能损失。

如果你使用第三个变量,你可以通过声明来帮助编译器:

register int temp;

XOR 技术(或加减法,如a-=b; b+=a; a=b-a;)的使用可以追溯到内存是一种关键资源并且保存堆栈条目可能非常重要的时候。这些天来,唯一的价值就是代码混淆。

我完全不知道 XOR 会对浮点值产生什么影响,但我怀疑它们可能会先转换为整数:您需要尝试一下,但不能保证所有编译器都会给出同样的结果。

【讨论】:

    【解决方案2】:

    对于较低级别(例如程序集)的“变量”不再具有永久位置。有时一个变量会在内存中,但有时同一个变量会在一个寄存器中,有时在不同的寄存器中。

    在生成代码时,编译器必须跟踪每个变量在每个点发生的位置。对于像a = b 这样的操作,如果b 在寄存器1 中,那么编译器只是决定a 现在也在寄存器1 中。无需移动或复制任何内容。

    现在考虑一下:

    // r0 contains variable a
    // r1 contains variable b
    
        temp = a
    
    // r0 contains variable a and variable temp
    // r1 contains variable b
    
        a = b
    
    // r0 contains variable temp
    // r1 contains variable b and variable a
    
        b = temp
    
    // r0 contains variable temp and variable b
    // r1 contains variable a
    

    如果您考虑这一点,您会意识到不需要移动或复制数据,也不需要生成代码。现代 CPU 能够极快地“无所事事”。

    注意:如果变量是局部变量(在堆栈上)并且不在寄存器中,那么编译器应该能够执行相同的“重命名”以避免移动任何内容。如果变量是全局变量,那么事情会变得更加复杂 - 很可能会导致每个全局变量从内存加载并存储到内存(例如,如果两个变量都是全局变量,则需要 2 次加载和 2 次存储)。

    对于 XOR(和加法/减法),编译器可能能够对其进行优化,使其变为无;但我不会指望它。临时变量移动更有可能得到很好的优化。

    当然,并不是所有的东西都小到可以放入一个寄存器。例如,也许你正在交换结构,它们每个是 1234 字节。在这种情况下,程序员可能会使用memcpy(),而编译器必须进行复制;或者程序员可能一次用异或(或加法/减法)做一个字段,编译器必须这样做。对于这些,memcpy() 更有可能得到更好的优化。但是,也许程序员更聪明,不会交换 1234 字节的数据,只交换指向数据的指针。指针适合寄存器(即使它们指向的数据不适合),因此可能不会为此生成任何代码。

    【讨论】:

      【解决方案3】:

      如果您正在操作整数或双精度数(或更一般地说,任何包含加法和减法运算符的类型),您可以这样做:

      int a = 5;
      int b = 7;
      a += b;    // a == 12, b == 7
      b = a - b; // a == 12, b == 5
      a -= b;    // a == 7,  b == 5
      

      【讨论】:

      • 但实际上并不适用于双打,请考虑 NaN 或 a=+inf、b=-inf 或 a=1e300、b=1。 XOR 适用于双打,在允许你这样做的语言中(例如带有 SSE 内在函数的 C)。
      • @harold : 很好的观察
      • 不适用于int,因为a += b 可能发生的溢出是未定义的行为。
      猜你喜欢
      • 1970-01-01
      • 2014-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-17
      • 1970-01-01
      相关资源
      最近更新 更多