优化正在取消我在 clang 6 中的整数溢出检查

Optimizations are killing my integer overflow checks in clang 6

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

#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

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

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

如果您想检测(有符号)integer overflows(在 int64_tlong 等标量类型上),您应该使用适当的内置函数,通常是特定于编译器的。

对于 GCC,请参阅 integer overflow builtins

整数溢出(在 intlong 或其他有符号整数类型上)根本是 undefined behavior, so the compiler can optimize as it please against it. Be scared. If you depend on UB you are no more coding in standard C++ and your program is tied to a particular compiler and system, so is not portable 的一个实例(甚至对其他编译器、其他编译器版本、其他编译标志、其他计算机和操作系统)。所以 Clang(或 GCC)被允许针对整数溢出进行优化,有时确实如此。

或者考虑使用一些bignum package (then of course you don't deal with just predefined C++ integral scalar types). Perhaps GMPlib

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

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

正如 @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;
    }
}

函数returnstrue如果整数乘法x * y预测溢出

请注意,虽然 unsigned 溢出是根据 modular arithmetic, signed overflow is an undefined behavior 明确定义的。尽管如此,所提供的函数也适用于 signedunsigned T 类型。