Оптимизации убивают мои проверки целочисленных переполнений в Clang 6 - PullRequest
0 голосов
/ 01 сентября 2018

У меня есть реализация с фиксированной запятой для какого-то финансового приложения. В основном это целое число, заключенное в класс, который основан на количестве десятичных знаков, заданных 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 ++ здесь . Там не исключение.

Обратите внимание, что исключение выдается в режиме отладки, но не в режиме выпуска.

Ответы [ 2 ]

0 голосов
/ 01 сентября 2018

Как уже указывалось @ 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;
    }
}

Функция возвращает true, если прогнозируется переполнение целочисленного умножения x * y.

Обратите внимание, что хотя переполнение unsigned четко определено в терминах модульной арифметики , переполнение signed представляет собой неопределенное поведение . Тем не менее, представленная функция работает и для типов signed и unsigned T.

0 голосов
/ 01 сентября 2018

Если вы хотите обнаружить (со знаком) целочисленные переполнения (для скалярных типов, таких как int64_t или long), вы должны использовать соответствующие встроенные функции, часто специфичные для компилятора.

Для GCC см. встроенные функции переполнения целых чисел .

Целочисленное переполнение (для простого int или long или другого целочисленного типа со знаком) является экземпляром неопределенного поведения , поэтому компилятор может оптимизировать его по своему усмотрению. , Быть напуганным . Если вы зависите от UB, вы больше не программируете в стандартном C ++, и ваша программа привязана к определенному компилятору и системе, поэтому она вообще не portable (даже для других компиляторов, других версий компилятора, других флагов компиляции , другие компьютеры и операционные системы). Поэтому Clang (или GCC) разрешается оптимизировать от переполнения целых чисел, а иногда и делает.

Или рассмотрите возможность использования некоторого пакета bignum (тогда, конечно, вы не имеете дело только с предопределенными интегральными скалярными типами C ++). Возможно GMPlib .

Вы можете использовать GCC __int128, если ваши числа укладываются в 128 бит.

Я полагаю, что вы не можете надежно обнаруживать целочисленные переполнения, когда они происходят (если вы не используете встроенные встроенные целочисленные переполнения ). Вам следует избегать их (или использовать некоторую библиотеку bignum, или некоторую библиотеку, использующую эти встроенные функции и т. Д.).

...