std::mt19937 在 GCC 中 std::uint_fast32_t 为 4 个字节时失败
std::mt19937 fails when std::uint_fast32_t is 4 bytes in GCC
我在尝试测试 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';
}
在我的机器上,它导致崩溃。我所说的“崩溃”是指该过程简单地挂起并在几秒钟后 returns 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>
有趣的部分是两个别名 std::uint_fast32_t
和 std::uint_fast64_t
的第一个 template
参数。这很有趣,因为深入研究 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;
};
我们可以清楚地看到,使用参数 1
构造的类型 _UIntType
的对象正在向左移动 __w
。为什么这很重要?让我们稍微回顾一下 std::mt19937
的实现。我们可以看到,最终,我们将做:
std::uint_fast32_t(1) << 32;
这可能没问题,除非...
除非sizeof (std::uint_fast32_t)
returns4
,因为它在我的机器上。然后我们处理 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
的 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,不进行左移。
至于崩溃本身:显然, and gives deterministic results (as you saw yourself). This may also be related to the cause of the crash. 遇到了类似的崩溃,同样是 Windows 上的 GCC 8.2。
作为解决方法,您可以使用 Boost.Random
库来代替,它实现了相同的 API.
我在尝试测试 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';
}
在我的机器上,它导致崩溃。我所说的“崩溃”是指该过程简单地挂起并在几秒钟后 returns 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>
有趣的部分是两个别名 std::uint_fast32_t
和 std::uint_fast64_t
的第一个 template
参数。这很有趣,因为深入研究 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;
};
我们可以清楚地看到,使用参数 1
构造的类型 _UIntType
的对象正在向左移动 __w
。为什么这很重要?让我们稍微回顾一下 std::mt19937
的实现。我们可以看到,最终,我们将做:
std::uint_fast32_t(1) << 32;
这可能没问题,除非...
除非sizeof (std::uint_fast32_t)
returns4
,因为它在我的机器上。然后我们处理 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
的 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,不进行左移。
至于崩溃本身:显然,
作为解决方法,您可以使用 Boost.Random
库来代替,它实现了相同的 API.