优化正在杀死我在 clang 6 中的整数溢出检查



我有一个用于某些金融应用程序的定点实现。它基本上是一个包装在一个类中的整数,该类基于给定的小数位数N被视为十进制数。该类是偏执狂并检查溢出,但是当我在发布模式下运行测试时,它们失败了,最后我创建了这个演示问题的最小示例:

#include <iostream>
#include <sstream>
template <typename T, typename U>
typename std::enable_if<std::is_convertible<U, std::string>::value, T>::type 
FromString(U&& str)
{
std::stringstream ss;
ss << str;
T ret;
ss >> ret;
return ret;
}
int main()
{
int NewAccu=32;
int N=10;
using T = int64_t;
T l = 10;
T r = FromString<T>("1" + std::string(NewAccu - N, '0'));
if (l == 0 || r == 0) {
return 0;
}
T res = l * r;
std::cout << l << std::endl;
std::cout << r << std::endl;
std::cout << res << std::endl;
std::cout << (res / l) << std::endl;
std::cout << std::endl;
if ((res / l) != r) {
throw std::runtime_error(
"FixedPoint Multiplication Overflow while upscaling [:" + std::to_string(l) + ", " + std::to_string(r) + "]");
}
return 0;
}

这发生在 Clang 6 中,我的版本是:

$ clang++ --version
clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

这很有趣,因为它是一个令人印象深刻的优化,但这破坏了我的应用程序并阻止我检测溢出。我能够在这里用 g++ 重现这个问题。它不会在那里引发异常。

请注意,异常在调试模式下引发,但不在发布模式下引发。

@Basile所述,有符号整数溢出是一种未定义的行为,因此编译器可以以任何方式处理它 - 甚至可以对其进行优化以获得性能优势。因此,在整数溢出发生后检测为时已晚。相反,您应该在整数溢出发生之前预测它。

这是我对整数乘法溢出预测的实现:

#include <limits>
template <typename T>
bool predict_mul_overflow(T x, T y)
{
static_assert(std::numeric_limits<T>::is_integer, "predict_mul_overflow expects integral types");
if constexpr (std::numeric_limits<T>::is_bounded)
{
return ((x != T{0}) && ((std::numeric_limits<T>::max() / x) < y));
}
else
{
return false;
}
}

如果整数乘法x * y预测为溢出,该函数将返回true

请注意,虽然unsigned溢出在模块化算术方面是明确定义的,但signed溢出是一种未定义的行为。尽管如此,所呈现的函数也适用于signedunsignedT类型。

如果要检测(有符号(整数溢出(在标量类型(如int64_tlong上(,则应使用适当的内置,通常是特定于编译器的。

对于 GCC,请参阅内置整数溢出。

整数溢出(在普通intlong或其他有符号整数类型上(是未定义行为的实例,因此编译器可以根据需要对其进行优化。害怕。如果您依赖 UB,则不再使用标准C++进行编码,并且您的程序绑定到特定的编译器和系统,因此根本不可移植(甚至可移植到其他编译器、其他编译器版本、其他编译标志、其他计算机和操作系统(。因此,允许 Clang(或 GCC(针对整数溢出进行优化,有时确实如此。

或者考虑使用一些 bignum 包(当然,您不会只处理预定义C++积分标量类型(。也许是GMPlib。

如果您的数字适合 128 位,您可以考虑使用 GCC 的__int128

我相信您无法在整数溢出发生时可靠地检测到它们(除非您使用内置的整数溢出(。你应该避免它们(或者使用一些bignum库,或者一些使用这些内置的库等(。

最新更新