The problem I have encountered occurs when I'm trying to test the cppreference example on generating pseudo-random numbers. Given the example:
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.
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:
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.
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.
Aucun commentaire:
Enregistrer un commentaire