【问题标题】:Another manner to swap the contents of two variables?交换两个变量内容的另一种方式?
【发布时间】:2012-09-15 12:39:06
【问题描述】:

昨天看到了下面这个函数。

static void swap (int *p, int *q) {
    *p -= *q;
    *q += *p;
    *p = *q - *p;
}

在什么情况下这个功能不起作用?我认为可能存在溢出问题,而且效率也可能很低。还有什么吗?

【问题讨论】:

  • 异或技巧可能吗?注意:p 和 q 可以指向同一个对象。
  • 是的,这些正是问题所在,溢出和混叠。
  • 谢谢,我没有考虑别名的情况。
  • 这只是一个个人问题,我阅读了几个交换两个变量的解决方案,我以前从未见过这种方法。所以我问为什么。这不是家庭作业。 ;)
  • @EdHeal:此外,编译器通常能够使用临时变量检测交换,并将其转换为单个适当的汇编指令(如果可用)。

标签: c pointers swap


【解决方案1】:

溢出问题不是“可能”,它肯定存在:当你用一个大的正数交换一个大的负数时,你会得到一个溢出,根据标准这是未定义的行为。

但最重要的是,出于可读性的原因,绝对没有必要使用它(或 xor 技巧)代替通常的交换(通过临时变量)。

【讨论】:

  • 0x7FFFFFFFE 需要 35 位,初始化时已经溢出。如果你初始化 pq 的数字不需要比 int 的宽度多的位数,则溢出时会有未定义的行为,但如果该行为恰好是回绕,它会起作用。
  • @DanielFischer 你说得对,我应该更仔细地计算我的比特。非常感谢您的指正!
  • 你应该已经编译了所有警告。如果您的编译器有任何好处,它会警告您“初始化溢出”;)
  • @DanielFischer 我为此使用了 ideone,它隐藏了编译时的警告,没有错误:)
  • 啊,“自己动手”的另一个原因。
【解决方案2】:

其实上面的答案是错误的。它为 p 获得 0xFFFFFFFE,因为它使用 0x7FFFFFFFE 初始化 q,它不适合并且(除了未定义的行为)实际上被截断为 0xFFFFFFFE。

因此,该答案中的代码实际上以 p = 0xFFFFFFFF 和 q = 0xFFFFFFFE 开头并成功交换了它们。

(编辑:错了,现已更正)

该例程实际上运行良好(除了我不确定是否存在的最终未定义行为,我对标准的细节有点模糊)。

忽略最终的未定义行为,如果基础算术是常见的 2 的补码模算术,则例程可以正常工作并且不会出现上溢/下溢问题(这是一种模算术)。

唯一真正的问题是别名。如果两个指针相同,则指向的整数将变为0。

因此,该例程的前提条件是两个指针有效且没有别名。如果您希望程序需要,则可以添加对空指针和相同指针的检查。

为什么要使用这种机制而不是 xor 技巧而不是临时变量交换的问题仍然悬而未决。但与 OP 问题无关:例程工作和溢出,因为它是由处理器上最常见的 2 的补码模算术处理的,这不是问题(编辑:它只是因为溢出是未定义的行为标准,但它适用于任何使用 2 的补码模运算的处理器)。至于效率,这完全取决于优化器和处理器。

【讨论】:

  • 有符号整数溢出是一种未定义的行为,它适用于大多数系统,这使得它变得更加阴险,因为人们可能会期望它会起作用。 (而且这个错误早就被纠正了,所以你的答案在这方面已经过时了。)
  • 正是出于这个原因,我确实写了“忽略最终未定义的行为”。你是对的,人们可能会期待它(未定义的行为意味着任何事情都可能发生,包括按预期工作),但关键是处理器算法使该算法有效。只是溢出是 C 中未定义的行为这一事实,尽管所有机器都使用模算术,这使得它值得怀疑。至于错误已被纠正的事实……不是我开始写作的时候。对不起,我打字很慢。
  • 溢出评论是因为您写了“我不确定是否存在”,只是为了澄清,而不是批评。至于打字慢,我可以同情,我也慢。关于“尽管所有机器都使用模算术”,这就是问题所在,而不是全部,不幸的是,仍然存在补码机器。
  • 对有符号整数运算使用反码的机器是否仍然对无符号整数使用模运算?如果是这样,算法可以用无符号整数指针编写并且可以工作(实际上,如果指针指向不是有符号整数的东西,只要大小相等,甚至可以工作)。它是 modulo 算术使其工作,而不是 2 的补码(1 的补码的问题在于它不是模算术)。我承认现在我只是出于好奇而问。
  • 是的,对于无符号整数,标准要求算术模 2^WIDTH,因此对于 unsigned*,唯一的问题是别名(当然,指针必须有效)。然而,使用unsigned* 交换其他类型的变量将再次成为未定义的行为(但是,据我所知,它失败的唯一方法是一种实际的可能性,即编译器基于假设严格别名进行优化)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-08
  • 2015-11-04
  • 1970-01-01
  • 2019-05-08
  • 2014-06-01
  • 1970-01-01
  • 2016-10-24
相关资源
最近更新 更多