std :: mt19937 завершается ошибкой, когда std :: uint_fast32_t равен 4 байта в GCC - PullRequest
0 голосов
/ 15 января 2019

Проблема, с которой я столкнулся, возникает, когда я пытаюсь проверить пример cppreference для генерации псевдослучайных чисел. Приведенный пример:

#include <iostream>
#include <random>

int main() {
    std::random_device rd{};
    std::mt19937 gen{rd()};
    std::uniform_int_distribution<> dis{1, 6};

    for(int n = 0; n < 10; ++n) {
        std::cout << dis(gen) << ' ';
    }
    std::cout << '\n';
}

на моей машине, это приводит к сбою. Под «крахом» я подразумеваю, что процесс просто зависает и возвращает 0xC0000005 через несколько секунд.

Мне было интересно, что может быть причиной этого. Ошибка GCC? Неисправность моей машины? Я решил проверить, и результаты были довольно удивительными. Например, с учетом следующего, слегка измененного примера:

#include <iostream>
#include <random>

int main() {
    std::random_device rd{};
    std::mt19937_64 gen{rd()}; // notice the _64 here
    std::uniform_int_distribution<> dis{1, 6};

    for(int n = 0; n < 10; ++n) {
        std::cout << dis(gen) << ' ';
    }
    std::cout << '\n';
}

Код работает как положено. Я попытался понять, почему, поэтому я быстро побежал к std::mt19937 ссылка , где мы можем увидеть его объявление:

template<
    class UIntType, 
    size_t w, size_t n, size_t m, size_t r,
    UIntType a, size_t u, UIntType d, size_t s,
    UIntType b, size_t t,
    UIntType c, size_t l, UIntType f
> class mersenne_twister_engine;

и два псевдонима:

using mt19937 = std::mersenne_twister_engine<std::uint_fast32_t, 32, 624, 397, 31, 
                         0x9908b0df, 11, 
                         0xffffffff, 7, 
                         0x9d2c5680, 15, 
                         0xefc60000, 18, 1812433253>

и

using mt19937_64 = std::mersenne_twister_engine<std::uint_fast64_t, 64, 312, 156, 31,
                         0xb5026f5aa96619e9, 29,
                         0x5555555555555555, 17,
                         0x71d67fffeda60000, 37,
                         0xfff7eee000000000, 43, 6364136223846793005>

Интересной частью является самый первый параметр template для обоих псевдонимов, std::uint_fast32_t и std::uint_fast64_t. Это интересно, потому что, погружаясь в реализацию GCC <random> , мы видим, что в строке 369 написано следующее:

__factor *= __detail::_Shift<_UIntType, 32>::__value;

С учетом реализации _Shift в строке 72:

template<typename _UIntType, size_t __w>
struct _Shift<_UIntType, __w, true> {
    static const _UIntType __value = _UIntType(1) << __w;
};

Мы можем ясно видеть, что объект типа _UIntType, построенный с аргументом 1, смещается на __w влево. Почему это имеет значение? Давайте вернемся немного к реализации std::mt19937. Мы можем видеть, что в конечном итоге мы будем делать:

std::uint_fast32_t(1) << 32;

что может быть хорошо, если только ...

Если sizeof (std::uint_fast32_t) не вернет 4, как это происходит на моей машине. Затем мы имеем дело с 32-битным (при условии, что байт = 8 бит) целочисленным значением без знака, которое будет смещено на 32 влево. Это неопределенное поведение , и я считаю, что это приводит к сбою моей программы.

Так что вопрос: это просто ошибка в некоторых реализациях GCC, где sizeof (std::uint_fast32_t) == 4? Или что-то слишком умное для меня происходит там, и это просто неисправность моей машины?

Я использую Windows 10, 64 бит, GCC 8.2 8.1.

Я попросил некоторых коллег выполнить несколько тестов, и все они прошли успешно (без сбоев). Дело в том, что на их машинах выражение sizeof (std::uint_fast32_t) оценивается как 8. Очевидно, что UB ушел.

РЕДАКТИРОВАТЬ : Еще более удивительным является то, что когда я заполняю gen с использованием некоторой константы, код ведет себя корректно, например, оба:

std::mt19937 gen{10000000};
std::uniform_int_distribution<> dis{1, 6};

for(int n = 0; n < 10; ++n) {
    std::cout << dis(gen) << ' ';
}

std::mt19937 gen{5};
std::uniform_int_distribution<> dis{1, 6};

for(int n = 0; n < 10; ++n) {
    std::cout << dis(gen) << ' ';
}
std::cout << '\n';

и

std::mt19937 gen{0};
std::uniform_int_distribution<> dis{1, 6};

for(int n = 0; n < 10; ++n) {
    std::cout << dis(gen) << ' ';
}
std::cout << '\n';

не в состоянии воспроизвести SEGFAULT. Мне удалось немного изменить пример. Рассмотрим следующий код:

#include <iostream>
#include <random>

int main() {
    std::random_device rd{};
    auto used = rd();
    std::cout << used << '\n';
}

Этот код последовательно выводит 3499211612. Дело в том ... Это НЕ работает (приводит к SEGFAULT):

#include <iostream>
#include <random>

int main() {
    std::random_device rd{};
    auto used = rd();
    std::cout << used << '\n';
    std::mt19937 gen{3499211612};
    std::uniform_int_distribution<> dis{1, 6};

    for(int n = 0; n < 10; ++n) {
        std::cout << dis(gen) << ' ';
    }
    std::cout << '\n';
}

Пока этот делает:

#include <iostream>
#include <random>

int main() {
    /*std::random_device rd{};
    auto used = rd();
    std::cout << used << '\n';*/
    std::mt19937 gen{3499211612};
    std::uniform_int_distribution<> dis{1, 6};

    for(int n = 0; n < 10; ++n) {
        std::cout << dis(gen) << ' ';
    }
    std::cout << '\n';
}

Есть идеи, как простой вызов std::random_device operator() изменяет поведение двигателя? Я чувствую, что должен задать другой вопрос, но я буквально не могу даже выразить словами то, что происходит в примерах ...

РЕДАКТИРОВАТЬ 2 :

g++ -v результат:

COLLECT_GCC = G ++

COLLECT_LTO_WRAPPER = C: / Пользователи / Felipe / настольные / MinGW / MinGW / бен /../ libexec / ССАГПЗ / x86_64-w64-mingw32 / 8.1.0 / LTO-wrapper.exe

Цель: x86_64-w64-mingw32

Настраивается с помощью: ../src/configure --enable-languages ​​= c, c ++ --build = x86_64-w64-mingw32 --host = x86_64-w64-mingw32 --target = x86_64-w64-mingw32 - disable-multilib --prefix = / c / temp / gcc / dest --with-sysroot = / c / temp / gcc / dest --disable-libstdcxx-pch --disable-libstdcxx-verbose --disable-nls - отключить общий доступ --disable-win32-registry --with-tune = haswell --enable-threads = posix --enable-libgomp

Модель резьбы: posix

gcc версия 8.1.0 (GCC)

1 Ответ

0 голосов
/ 15 января 2019

Код, который вы показали, не является причиной сбоя. Полное определение _Shift:

template<typename _UIntType, size_t __w,
     bool = __w < static_cast<size_t>
          (std::numeric_limits<_UIntType>::digits)>
  struct _Shift
  { static const _UIntType __value = 0; };

template<typename _UIntType, size_t __w>
  struct _Shift<_UIntType, __w, true>
  { static const _UIntType __value = _UIntType(1) << __w; };

При этом используется специализация шаблона для проверки размера _UIntType во время компиляции. Первая версия используется, когда __w больше или равно std::numeric_limits<_UIntType>::digits, как здесь. Таким образом, полученное значение равно 0, и сдвиг влево не выполняется.

Что касается самого сбоя: очевидно, std::random_device не работает в Windows GCC и дает детерминированные результаты (как вы сами видели). Это также может быть связано с причиной аварии. В этом вопросе произошел аналогичный сбой, также с GCC 8.2 в Windows.

В качестве обходного пути вы можете использовать библиотеку Boost.Random, которая реализует тот же API.

...