【问题标题】:Signed Integer value overflow in C++?C ++中的有符号整数值溢出?
【发布时间】:2018-09-07 09:07:22
【问题描述】:

我有一个遗留代码库,我们正在尝试将其从 devtoolset-4 迁移到 devtoolset-7。我注意到一个关于有符号整数溢出的有趣行为(具体来说是int64_t)。

有一个代码 sn-p 用于在乘以一大组整数时检测整数溢出:

// a and b are int64_t
int64_t product = a * b; 
if (b != 0 && product / b != a) {
    // Overflow
}

这段代码在 devtoolset-4 上运行良好。但是,使用 devtoolset-7,永远不会检测到溢出。

例如:当a = 83802282034166b = 98765432 时, product 变为 -5819501405344925872(显然值已溢出)。

product / b 的值等于a (83802282034166)。因此if 条件永远不会变为真。 它的值应该是根据溢出(负)product 值计算出来的:-5819501405344925872 / 98765432 = -58922451788

具有讽刺意味的是,数学是正确的,但它导致 devtoolset-4 的异常行为。

  • 编译器是否会缓存导致此行为的值(而不是重新评估它)?
  • 或者编译器优化转换语句product / b != aproduct != a * b 并达到相同的溢出值(或者可能只是跳过基于上面product = a * b 的语句的计算)?

我了解有符号整数溢出是 C++ 中的“未定义行为”,因此编译器行为可能会因实现而改变。但是有人可以帮我理解上述行为吗?

注意:devtoolset-4 和 devtoolset-7 中的 g++ 版本分别为 g++ (GCC) 5.2g++ (GCC) 7.2.1

【问题讨论】:

  • signed 整数溢出在 C++ 中未定义。事后您无法可靠地检测到它,因为您将隐含地处于未定义的行为中..
  • 一个正确的测试检查之前相乘,以避免未定义的行为。类似if (std::numeric_limits<std::int64_t>::max() / b < a) { /* error */ }
  • product == a * b,所以product / b != a可以优化为 -> a * b / b != a -> a * 1 != a -> a != a -> false
  • OP 声明他知道有符号整数溢出是 UB。链接到答案状态为“这是 UB”的问题没有用。
  • 如果您有兴趣了解编译器对给定代码段的输出,您可以使用 godbolt.org。例如,我有tried your snippet,似乎条件已被 gcc 7.3 与 -O2 完全优化。要解释为什么未定义的行为会发生作用,您需要准确指定您正在使用的编译器、正在使用的版本、正在使用的标志,答案仍可能取决于其他因素。

标签: c++ integer-overflow devtoolset


【解决方案1】:

有符号整数溢出是 C++ 中未定义的行为。

这意味着优化器可以假设它永远不会发生。 a*b/ba,句号。

现代编译器进行基于静态单一赋值的优化。

// a and b are int64_t
int64_t product = a * b;
if (b != 0 && product / b != a) {
  // Overflow
}

变成:

const int64_t __X__ = a * b; 
const bool __Y__ = b != 0;
const int64_t __Z__ = __X__ / b;
const int64_t __Z__ = a*b / b;
const int64_t __Z__ = a;

if (__Y__ && __Z__ != a) {
  // Overflow
}

计算结果为

if (__Y__ && false) {
  // Overflow
}

显然,__Z__aa!=afalse

int128_t big_product = a * b; 

使用big_product 并检测那里的溢出。

SSA 允许编译器实现(a+1)>a 始终为真,这可以简化许多循环和优化案例。这个事实依赖于有符号值溢出是不正当行为这一事实。

【讨论】:

    【解决方案2】:

    因为有符号的上溢/下溢被归类为未定义的行为,所以允许编译器作弊并假设它不会发生(这在一两年前的 Cppcon 演讲中出现,但我忘记了我的顶部的谈话头)。因为您在进行算术运算然后检查结果,所以优化器会优化掉部分检查。

    这是未经测试的代码,但您可能需要以下代码:

    if(b != 0) {
        auto max_a = std::numeric_limits<int64_t>::max() / b;
        if(max_a < a) {
            throw std::runtime_error{"overflow"};
        }
    }
    return a * b;
    

    请注意,此代码不处理下溢;如果a * b 可以为负数,则此检查无效。

    根据Godbolt,您可以看到您的版本已完全优化了检查。

    【讨论】:

    • 谢谢@stephen。 Godbolt 链接回答了我的问题。所以这确实是由于编译器优化。如果你能给我 Cppcon 谈话链接也很好。
    • 不应该是a &gt; max_a吗?
    • 并且否定也没有处理(-big * -bigbig * -big
    • @Aconcagua - 很确定我的检查是正确的。 max_a 应该是int64_t 可以存储的最大可能值,所以a 不可能更大。
    • @Jarod42 - 很好地抓住了底片。我会把它作为练习留给读者:)
    【解决方案3】:

    知道product == a * b,编译器/优化器可以采取以下优化步骤:

    b != 0 && product / b != a
    b != 0 && a * b / b != a
    b != 0 && a * 1 != a
    b != 0 && a != a
    b != 0 && false
    false
    

    优化器可以选择完全删除分支。


    我了解有符号整数溢出是 C++ 中的“未定义行为”,因此编译器行为可能会因实现而改变。但是有人可以帮我理解上述行为吗?

    您可能知道有符号整数溢出是 UB,但我想您还没有掌握 UB 的真正含义。 UB 不需要,而且通常没有意义。不过,这种情况似乎很简单。

    【讨论】:

      【解决方案4】:

      有人可以帮我理解上述行为吗?

      有符号整数溢出在 C++ 中具有未定义的行为。这意味着您无法可靠地检测到它,并且包含有符号整数溢出的代码可以做任何事情


      如果你想检测一个操作是否会导致有符号整数溢出,你需要在溢出发生之前做,防止UB发生。

      【讨论】:

      • 已经有多个重复了,这个答案有用吗?
      • 不,让我们关闭它。
      【解决方案5】:

      Signed integer overflow is undefined behavior。这与unsigned int(所有无符号整数)不同。 更多信息here

      作为旁注,人们注意到使用int 而不是unsigned int 可以提高性能(请参阅here),因为编译器不处理溢出行为。

      【讨论】:

      • 已经有多个重复了,这个答案有用吗?
      【解决方案6】:

      如果您担心整数溢出,最好不要使用任意精度的整数库 - 这样您可以将大小类型增加到 128 位,而不必担心。

      https://gmplib.org/

      【讨论】:

        【解决方案7】:

        您可以阅读此文档,它可能对您有用,就好像我在变量和数据类型方面遇到任何问题一样,我会直接阅读它:http://www.cplusplus.com/doc/tutorial/variables/

        【讨论】:

          猜你喜欢
          • 2021-03-15
          • 2013-04-10
          • 1970-01-01
          • 2011-05-13
          • 2014-12-09
          • 1970-01-01
          • 2017-05-13
          • 2012-02-29
          • 1970-01-01
          相关资源
          最近更新 更多