mardi 15 janvier 2019

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

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