std::mt19937 fails when std::uint_fast32_t is 4 bytes in GCC

  • A+
Category:Languages

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.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: