我有一个遗留代码库,我们正试图从devtoolset-4
迁移到devtoolset-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 = 83802282034166
和b = 98765432
时,
product
变为-5819501405344925872
(显然值已溢出)。
但product / b
导致的价值等于a (83802282034166)
。因此if
条件永远不会成立。
其值应根据溢出(负)product
值计算:-5819501405344925872 / 98765432 = -58922451788
具有讽刺意味的是,数学是正确的,但它会导致与devtoolset-4相关的异常行为。
product / b != a
到product != a * b
并达到相同的溢出值(或者可能只是根据product = a * b
上面的语句跳过计算?我知道有符号整数溢出是一种未定义的行为'在C ++中,编译器行为可能会在实现中发生变化。但有人可以帮助我理解上述行为吗?
注意:devtoolset-4和devtoolset-7中的g ++版本分别为g++ (GCC) 5.2
和g++ (GCC) 7.2.1
。
答案 0 :(得分:6)
有符号整数溢出是C ++中未定义的行为。
这意味着优化器可以假设它永远不会发生。 a*b/b
为a
,期间。
现代编译器进行基于静态单一赋值的优化。
// 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__
为a
而a!=a
为false
。
int128_t big_product = a * b;
使用big_product
并检测那里的溢出。
SSA允许编译器实现(a+1)>a
始终为真的事情,这可以简化许多循环和优化案例。这个事实依赖于签名值的溢出是不可取的行为。
答案 1 :(得分: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,您可以看到您的版本已完全优化了检查。
答案 2 :(得分: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
优化器可以选择完全删除分支。
我知道有符号整数溢出是一种未定义的行为&#39;在C ++中,编译器行为可能会在实现中发生变化。但有人可以帮助我理解上述行为吗?
你可能知道有符号整数溢出是UB,但我想你还没有掌握UB的真正含义。 UB并不需要,而且往往没有意义。这个案子似乎很直接。
答案 3 :(得分:0)
有人可以帮助我理解上述行为吗?
有符号整数溢出在C ++中有未定义的行为。这意味着您无法可靠地检测到它,并且包含有符号整数溢出的代码可以执行任何操作。
如果要检测操作是否会导致有符号整数溢出,则需要在溢出发生之前执行此操作,以防止UB发生。
答案 4 :(得分:0)
Signed integer overflow is undefined behavior。这与unsigned int
(所有无符号整数)不同。
有关此here
作为旁注,人们注意到使用int
代替unsigned int
会提高性能(请参阅here),因为编译器不会处理溢出行为。
答案 5 :(得分:0)
如果你担心整数溢出,那么使用任意精度整数库并不是更好 - 用这个你可以将你的大小类型增加到128位而不用担心它。
答案 6 :(得分:0)
您可以阅读本文档,它可能对您有用,就像我在变量和数据类型中遇到的任何问题一样,我直接阅读它:http://www.cplusplus.com/doc/tutorial/variables/