当std :: uint_fast32_t在GCC中为4个字节时,std :: mt19937失败

时间:2019-01-15 15:47:28

标签: c++ gcc random c++17 mt19937

当我尝试测试生成伪随机数的cppreference 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';
}

在我的计算机上,它导致崩溃。 “崩溃”是指该过程仅挂起并在几秒钟后返回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 reference,在这里可以看到其声明:

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>

有趣的部分是两个别名templatestd::uint_fast32_t的第一个std::uint_fast64_t参数。有趣的是,深入GCC <random> implementation,我们可以看到在369行中,写了以下内容:

__factor *= __detail::_Shift<_UIntType, 32>::__value;

给出72行中的_Shift implementation

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。这是undefined behaviour,我相信这会导致我的程序崩溃。

所以问题是:在sizeof (std::uint_fast32_t) == 4中的某些GCC实现中,这是否仅仅是一个错误?还是在那里发生了对我来说太聪明的事情,这仅仅是我的机器的故障?

我正在使用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的{​​{1}}会改变引擎的行为吗?我觉得我应该问另一个问题,但实际上我根本无法说出示例中发生的事情……

编辑2

operator()结果:

  

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

     

目标: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)

1 个答案:

答案 0 :(得分:9)

您显示的代码不是导致崩溃的原因。 _Shift的完整定义是:

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; };

这使用模板特化来在编译时检查_UIntType的大小。当__w大于或等于std::numeric_limits<_UIntType>::digits时使用第一种版本,在这种情况下就是这样。因此,结果值为0,并且不执行左移。

关于崩溃本身:显然,std::random_device doesn't work on Windows GCC并给出确定的结果(如您所见)。这也可能与崩溃原因有关。 This question也在Windows中使用GCC 8.2时也遇到了类似的崩溃。

作为一种解决方法,您可以使用Boost.Random库来实现相同的API。