Fur*_*ish 12 c++ random gcc c++17 mt19937
我遇到的问题发生在我试图测试生成伪随机数的cppreference示例时.举个例子:
#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';
}
Run Code Online (Sandbox Code Playgroud)
在我的机器上,它会导致崩溃.通过"崩溃"我的意思是该过程只是挂起并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';
}
Run Code Online (Sandbox Code Playgroud)
代码按预期工作.我试着理解为什么,所以我很快跑去std::mt19937参考,在那里我们可以看到它的声明:
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;
Run Code Online (Sandbox Code Playgroud)
后跟两个别名:
using mt19937 = std::mersenne_twister_engine<std::uint_fast32_t, 32, 624, 397, 31,
0x9908b0df, 11,
0xffffffff, 7,
0x9d2c5680, 15,
0xefc60000, 18, 1812433253>
Run Code Online (Sandbox Code Playgroud)
和
using mt19937_64 = std::mersenne_twister_engine<std::uint_fast64_t, 64, 312, 156, 31,
0xb5026f5aa96619e9, 29,
0x5555555555555555, 17,
0x71d67fffeda60000, 37,
0xfff7eee000000000, 43, 6364136223846793005>
Run Code Online (Sandbox Code Playgroud)
有趣的部分是template两个别名的第一个参数,std::uint_fast32_t和std::uint_fast64_t.这很有意思,因为深入了解GCC <random>实现,我们可以看到,在这一行中369,写了以下内容:
__factor *= __detail::_Shift<_UIntType, 32>::__value;
Run Code Online (Sandbox Code Playgroud)
鉴于在线_Shift实施72:
template<typename _UIntType, size_t __w>
struct _Shift<_UIntType, __w, true> {
static const _UIntType __value = _UIntType(1) << __w;
};
Run Code Online (Sandbox Code Playgroud)
我们可以清楚地看到,_UIntType使用参数构造的类型的对象1正在__w向左移动.为什么这么重要?让我们回过头来看一下std::mt19937实现.我们可以看到,最终,我们将做:
std::uint_fast32_t(1) << 32;
Run Code Online (Sandbox Code Playgroud)
哪个可能没关系,除非......
除非sizeof (std::uint_fast32_t)返回4,就像在我的机器上一样.然后我们处理32位(假设字节= 8位)无符号整数值,该值将向左移位32.这是未定义的行为,我相信这会导致我的程序崩溃.
所以问题是:它只是一些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) << ' ';
}
Run Code Online (Sandbox Code Playgroud)
,
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';
Run Code Online (Sandbox Code Playgroud)
和
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';
Run Code Online (Sandbox Code Playgroud)
无法重现SEGFAULT.我设法改变了一些例子.请考虑以下代码:
#include <iostream>
#include <random>
int main() {
std::random_device rd{};
auto used = rd();
std::cout << used << '\n';
}
Run Code Online (Sandbox Code Playgroud)
该代码连续产生输出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';
}
Run Code Online (Sandbox Code Playgroud)
虽然这个做了:
#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';
}
Run Code Online (Sandbox Code Playgroud)
任何想法如何简单地调用std::random_device的operator()改变发动机的bahaviour?我觉得我应该问另一个问题,但我甚至无法说出示例中发生的事情......
编辑2:
g++ -v 结果:
COLLECT_GCC =克++
COLLECT_LTO_WRAPPER = C:/用户/菲利普/桌面/ 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-shared --disable-win32-registry --with-tune = haswell --enable-threads = posix --enable-libgomp
线程模型:posix
gcc版本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; };
Run Code Online (Sandbox Code Playgroud)
这使用模板特化来检查_UIntType编译时的大小.当__w大于或等于时std::numeric_limits<_UIntType>::digits,使用第一个版本,这是这里的情况.因此结果值为0,并且不执行左移.
至于崩溃本身:显然,std::random_device在Windows GCC上不起作用并给出确定性结果(如您所见).这也可能与坠机原因有关.这个问题遇到了类似的崩溃,也与Windows上的GCC 8.2有关.
作为一种变通方法,您可以使用Boost.Random库来实现相同的API.