The problem I have encountered occurs when I'm trying to test the cppreference example on generating pseudo-random numbers. Given the example:
#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';
}
on my machine, it results in a crash. By "crash" I mean that the process simply hangs and returns 0xC0000005
after few seconds.
I was wondering what might be causing it. GCC bug? My machine's malfunction? I decided to test and the results were quite surprising. For example, given the following, slightly modified example:
#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';
}
The code works as expected. I tried to understand why, so I quickly ran to std::mt19937
reference, where we can see its declaration:
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;
followed by two aliases:
using mt19937 = std::mersenne_twister_engine<std::uint_fast32_t, 32, 624, 397, 31,
0x9908b0df, 11,
0xffffffff, 7,
0x9d2c5680, 15,
0xefc60000, 18, 1812433253>
and
using mt19937_64 = std::mersenne_twister_engine<std::uint_fast64_t, 64, 312, 156, 31,
0xb5026f5aa96619e9, 29,
0x5555555555555555, 17,
0x71d67fffeda60000, 37,
0xfff7eee000000000, 43, 6364136223846793005>
The interesting part is the very first template
parameter for both aliases, the std::uint_fast32_t
and std::uint_fast64_t
. It's interesting because, diving into GCC <random>
implementation, we can see that, in the line 369
, the following is written:
__factor *= __detail::_Shift<_UIntType, 32>::__value;
Given the _Shift
implementation at line 72
:
template<typename _UIntType, size_t __w>
struct _Shift<_UIntType, __w, true> {
static const _UIntType __value = _UIntType(1) << __w;
};
We can clearly see that an object of a type _UIntType
, constructed with an argument 1
is being shifted by __w
to the left. Why does that matter? Let's go back a little bit to the std::mt19937
implementation. We can see that ultimately, we will be doing:
std::uint_fast32_t(1) << 32;
which might be okay, unless...
Unless the sizeof (std::uint_fast32_t)
returns 4
, as it does on my machine. We are then dealing with 32 bit (assuming byte = 8 bits) unsigned integer value that is going to be shifted by 32 to the left. This is undefined behaviour and I believe this causes my program to crash.
So the question is: Is it simply a bug in some GCC implementations where sizeof (std::uint_fast32_t) == 4
? Or something too clever for me is happening there and it's just my machine's malfunction?
I am using Windows 10, 64 bit, GCC 8.2 8.1.
I have asked some colleagues to run some tests and every one of them succeded (no crashes). The thing is that on their machines the expression sizeof (std::uint_fast32_t)
evaluated to 8
. Obviously, the UB is then gone.
EDIT: What's even more surprising is that when I seed the gen
using some constant, the code behaves correctly, for example both:
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';
and
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';
fail to reproduce the SEGFAULT. I managed to alter the example a little bit. Consider the following code:
#include <iostream>
#include <random>
int main() {
std::random_device rd{};
auto used = rd();
std::cout << used << '\n';
}
This code consecutively produces the output 3499211612
. The thing is... This does NOT work (results in 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';
}
While this one does:
#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';
}
Any idea how simply calling the std::random_device
's operator()
alters the bahaviour of the engine? I feel like I should ask another question, but I am literally unable to even put into words what is happening in the examples...
EDIT 2:
g++ -v
result:
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=c:/users/felipe/desktop/mingw/mingw/bin/../libexec/gcc/x86_64-w64-mingw32/8.1.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../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-shared --disable-win32-registry --with-tune=haswell --enable-threads=posix --enable-libgomp
Thread model: posix
gcc version 8.1.0 (GCC)
The code you've shown is not the cause for the crash. The full definition of _Shift
is:
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; };
This uses template specialization to check the size of _UIntType
at compile-time. The first version is used when __w
is greater than or equal than std::numeric_limits<_UIntType>::digits
, which is the case here. So the resulting value is 0, and the left-shift isn't performed.
As for the crash itself: Apparently, std::random_device
doesn't work on Windows GCC and gives deterministic results (as you saw yourself). This may also be related to the cause of the crash. This question encountered a similar crash, also with GCC 8.2 on Windows.
As a workaround, you can use the Boost.Random
library instead which implements the same API.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With