我有一个遗留代码库,我们正在尝试从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。
发布于 2018-03-28 15:43:11
由于签名溢出/下溢被归类为未定义的行为,编译器可以欺骗并假设它不会发生(这是在一两年前的一次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可能为负值,则此检查将无效。
根据哥德波特,您可以看到您的版本已经完全优化了检查。
发布于 2018-03-28 15:45:33
有符号整数溢出是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这样的东西总是正确的,这可以简化许多循环和优化情况。这个事实依赖于一个事实,即有符号值溢出是不可侵犯的行为。
发布于 2018-03-28 15:54:04
有了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不需要,而且常常没有意义。不过,这个案子似乎很直接。
https://stackoverflow.com/questions/49538595
复制相似问题