Проблема, с которой я столкнулся, возникает, когда я пытаюсь проверить пример 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)