5

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

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

// 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并达到相同的溢出值(或者可能只是跳过基于上述语句 where 的计算product = a * b)?

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

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

4

7 回答 7

6

有符号整数溢出是 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!=a原样false

int128_t big_product = a * b; 

在那里工作big_product并检测溢出。

(a+1)>aSSA 允许编译器实现总是正确的事情,这可以简化许多循环和优化案例。这个事实依赖于有符号值的溢出是不受约束的行为这一事实。

于 2018-03-28T15:45:33.137 回答
4

因为有符号的上溢/下溢被归类为未定义的行为,所以允许编译器作弊并假设它不会发生(这是在一两年前的 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,您可以看到您的版本已完全优化了检查。

于 2018-03-28T15:43:11.703 回答
4

知道了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 不需要,而且通常没有意义。不过,这种情况似乎很简单。

于 2018-03-28T15:54:04.953 回答
0

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

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


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

于 2018-03-28T15:41:54.440 回答
0

有符号整数溢出是未定义的行为。这与unsigned int(所有无符号整数)不同。有关此的更多信息here

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

于 2018-03-28T15:42:41.097 回答
0

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

https://gmplib.org/

于 2018-03-28T15:48:07.907 回答
0

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

于 2018-03-28T16:15:26.723 回答