【问题标题】:std::mt19937 fails when std::uint_fast32_t is 4 bytes in GCC当 std::uint_fast32_t 在 GCC 中为 4 字节时,std::mt19937 失败
【发布时间】:2019-06-09 15:59:09
【问题描述】:

我在尝试测试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

我想知道可能是什么原因造成的。海湾合作委员会错误?我的机器故障?我决定进行测试,结果非常令人惊讶。例如,给定以下稍作修改的示例:

#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>

有趣的部分是两个别名的第一个 template 参数,std::uint_fast32_tstd::uint_fast64_t。有趣的是,深入GCC &lt;random&gt; implementation,我们可以看到,在369 行中,写了以下内容:

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

鉴于_Shift implementation 位于72 行:

template<typename _UIntType, size_t __w>
struct _Shift<_UIntType, __w, true> {
    static const _UIntType __value = _UIntType(1) << __w;
};

我们可以清楚地看到,使用参数1 构造的_UIntType 类型的对象正在向左移动__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_deviceoperator() 会如何改变引擎的行为吗?我觉得我应该问另一个问题,但我什至无法用语言表达示例中发生的事情......

编辑 2

g++ -v 结果:

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)

【问题讨论】:

  • 你说“崩溃”。您能否提供有关此崩溃的详细信息?
  • 进程返回0xC0000005。将包括在问题中。
  • sizeof (std::uint_fast32_t) evaluated to 8 令人费解。 32 不是指 4 个字节吗?
  • 不,因为没有要求。这就是uint32_tuint_fast32_t 之间的区别。后者不需要是 32 位。它必须至少为 32 位。
  • 这对我来说似乎是一个错误。

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


【解决方案1】:

您显示的代码不是导致崩溃的原因。 _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&lt;_UIntType&gt;::digits 时使用,这里就是这种情况。所以结果值为0,不执行左移。

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

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

【讨论】:

  • +1'ed,但它没有解决我的问题。我进行了编辑,但我觉得我应该问一个新问题,因为它完全改变了我对问题的看法。我可以问问你的意见吗?我应该删除编辑并提出一个新问题吗?
  • 虽然我认为我也应该接受这个答案,因为它回答了原始问题 - 这不是关于 std::uing_fast32_t 的错误,它是 something 其他的,这似乎是一个足够好的总结来打开一个新问题。不过我会等待回复。
  • @Fureeish 我认为这是一个很好的问题,不应该被删除。至于打开另一个关于崩溃原因的问题,似乎已经有人问过了:stackoverflow.com/questions/54042937/… - 在 Windows 上也使用 GCC 8.2。显然random_device 在 Windows GCC 上是错误的(正如你所看到的,即使它没有崩溃,它实际上也没有给出随机数)。我会更新我的答案以反映这一点。
  • 谢谢!另外,非常感谢您的建议。
猜你喜欢
  • 1970-01-01
  • 2014-08-21
  • 1970-01-01
  • 1970-01-01
  • 2016-11-17
  • 2021-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多