我在尝试测试 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_t
和 std::uint_fast64_t
.这很有趣,因为潜入 GCC <random>
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;
};
我们可以清楚地看到 _UIntType
类型的对象, 使用参数 1
构造正在移动 __w
向左转。为什么这很重要?让我们回到 std::mt19937
执行。我们可以看到,最终我们会做:
std::uint_fast32_t(1) << 32;
这可能没问题,除非...
除非 sizeof (std::uint_fast32_t)
返回 4
,就像在我的机器上一样。然后我们处理 32 位(假设字节 = 8 位)无符号整数值,它将向左移动 32。这是undefined behaviour我相信这会导致我的程序崩溃。
所以问题是:这是否只是某些 GCC 实现中的一个错误,其中 sizeof (std::uint_fast32_t) == 4
?还是那里发生了对我来说太聪明的事情,而这只是我的机器故障?
我使用的是 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
的operator()
改变引擎的行为?我觉得我应该问另一个问题,但我什至无法用语言表达示例中发生的事情......
编辑 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
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)
最佳答案
您显示的代码不是导致崩溃的原因。 _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 的库。
关于c++ - 当 std::uint_fast32_t 在 GCC 中为 4 字节时,std::mt19937 失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54202263/