【问题标题】:Big number's addition resultin Gcc ( Abelian group ) [closed]Gcc(阿贝尔群)中的大数加法结果[关闭]
【发布时间】:2012-07-13 18:41:04
【问题描述】:

我正在阅读有关在 C 中检测溢出的技术。显示检测溢出的错误解决方案的示例之一是:

/* Determine whether arguments can be added without overflow */
int tadd_ok(int x, int y) {
    int sum = x+y;
    return (sum-x == y) && (sum-y == x);
}

它说它不起作用,因为:

二的补码加法形成一个阿贝尔群,因此 表达式 (x+y)-x 将计算为 y 而不管是否 加法溢出,并且 (x+y)-y 将始终计算为 x

这到底是什么意思?这是否意味着C编译器将sum替换为x+y
为了弄清楚它在说什么,我什至跟踪了程序的汇编代码,但没有替换的迹象。

更新:我的问题的本质是,GCC 是否在不计算表达式的情况下评估它?
不是关于二进制补码的问题。
您可以在here 中看到示例输出。

【问题讨论】:

  • “阿贝尔群”只是“环”的一个花哨的数学术语。具有通常溢出行为的二进制补码整数形成整数“环”。当你加/减时,你沿着环移动。因此,无论溢出如何,您都可以随时向相反方向移动。
  • 你的意思是说,看到有符号溢出给出了未指定的结果,这种“损坏的溢出检测方法”可能由于使用未指定的优化而意外地起作用?
  • @ArashThr:那我不确定你在这里问什么!编译器可以简化算术表达式,但他们没有必须这样做。我不确定这与阿贝尔群有什么关系。
  • 正如 Oli 所说,它们不是必需的或强制的,但是自从您询问 gcc 以来,它通常会这样做,而且我已经看到它用一个结果替换了许多代码行和一个很长的循环。因为它都是静态的东西,它预先计算了结果并简单地用结果填充寄存器,删除了大量的代码。
  • 问题更新值得更改标题、标签和更好的措辞,因为问题开始谈论 2 的补码溢出并最终询问 gcc 行为。

标签: c optimization assembly compiler-construction


【解决方案1】:

如果您举一个简单的4 (0b0100) + 5 (0b0101) 示例,您可以看到无符号和应该是9 (1001),这实际上是二进制补码中的-7。如果你然后取那个总和 (0b1001) 并使用二进制补码算法从中减去 4:

    0b1001 - 0b0100 = 0b1001 + 2s_complement(0b0100) = 0b1001 + 0b1100 = 0b1_0101 

您最终得到 0101,即 5(您在 2 的补码操作期间丢弃溢出的最重要的 1)。总和减去 5 等于 4:

    0b1001 - 0b0101 = 0b1001 + 2s_complement(0b0101) = 0b1001 + 0b1011 = 0b1_0100

这满足您提供的 c 代码但仍然导致溢出。

来自维基百科关于two's complement的文章:

二进制补码十进制 0111 7 0110 6 0101 5 0100 4 0011 3 0010 2 0001 1 0000 0 1111 -1 1110 -2 1101 -3 1100 -4 1011 -5 1010 -6 1001 -7 1000 -8

更新:
为了演示您的 INT_MAX 示例,使用我的 INT_MAX = 7 的普通 4 位整数系统,我们可以看到与您的 c 代码相同的结果。

    7 + 7 (0b0111 + 0b0111) = 0b1110 (-2 in two's complement)

就像我上面的例子一样,减去sum - 7 将等于7

    0b1110 - 0b0111 = 0b1110 + 2s_complement(0b0111) = 0b1110 + 0b1001 = 0b1_0111

【讨论】:

    【解决方案2】:

    看起来会导致错误结果的错误也会影响后续测试的双方,因此测试永远不会失败。

    【讨论】:

      【解决方案3】:

      如果您的编译器有优化(即 -02),则此功能可能不起作用。但是,如果没有优化,我相信这会正常工作。

      【讨论】:

      • 实际上正好相反。这几乎肯定不会起作用(假设硬件正常),除非编译器做了一些有趣的事情。
      【解决方案4】:

      我可能错了,但这在我看来就像一个简单的溢出检查...如果 sum 溢出,sum-x 不能等于 y

      更多:参数在堆栈中传递给函数,sum 是一个局部变量,它也在堆栈上。编译器可能会将 sum=0, sum+x 和它们 sum+y(堆栈位置 + 另一个堆栈位置)放入 sum 位置。对于 return 语句,它可能会通过复制 sum 变量将结果放入另一个临时位置

      【讨论】:

      • 我相信 OP 很清楚这一点。
      • 那很抱歉我没看懂问题
      【解决方案5】:

      关于您发布的代码,它不会在所有平台上运行,因为某些平台会隐式环绕溢出。该表达式将按以下方式计算:

      int x    = 0xFFFFFFF0;
      int y    = 0x00000020;
      
      int sum  = x + y;   // value of sum is 0x00000010
                          // would be 0x100000010, but highest order bit is dropped
      
      int diff = sum - y; // value of diff is 0xFFFFFFF0
                          // equal to x
      

      域是封闭的,即溢出将以可预测和可逆的方式环绕。您不能依赖此方法来检查溢出。

      也就是说,这种行为是特定于平台的。一些处理器会这样做,而其他处理器会返回最接近的可表示值(即 sum 等于 0xFFFFFFFF),在这种情况下,它会按预期工作。

      在 C 中检查溢出的最佳方法是使用内联汇编语句将标志压入堆栈,将它们弹出到寄存器中,然后返回值。如果上一次数学运算发生溢出,则设置其中一个标志。我以前这样做过,但不记得怎么做。请参阅以下页面获取资源和帮助:

      http://en.wikipedia.org/wiki/FLAGS_register_(computing)

      Read flag register from C program

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-07-18
        • 1970-01-01
        • 2014-11-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-13
        相关资源
        最近更新 更多