我在 Ubuntu 20.04 LTS 上使用 clang++ 10,-fsanitize-undefined-trap-on-error -fsanitize=address,undefined,nullability,implicit-integer-truncation,implicit-integer-arithmetic-value-change,implicit-conversion,integer
我的代码正在生成随机字节
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint8_t> dd(0, 255);
...
ch = uint8_t(dd(gen));
最后一行导致消毒程序报告未定义的行为位于bits/random.tcc中
template<...> void mersenne_twister_engine<...>::
_M_gen_rand(void) {
const _UIntType __upper_mask = (~_UIntType()) << __r;
const _UIntType __lower_mask = ~__upper_mask;
for (size_t __k = 0; __k < (__n - __m); ++__k)
{
_UIntType __y = ((_M_x[__k] & __upper_mask)
| (_M_x[__k + 1] & __lower_mask));
_M_x[__k] = (_M_x[__k + __m] ^ (__y >> 1)
^ ((__y & 0x01) ? __a : 0));
}
for (size_t __k = (__n - __m); __k < (__n - 1); ++__k)
{
_UIntType __y = ((_M_x[__k] & __upper_mask)
| (_M_x[__k + 1] & __lower_mask));
_M_x[__k] = (_M_x[__k + (__m - __n)] ^ (__y >> 1) <<<<===== this line
^ ((__y & 0x01) ? __a : 0));
}
_UIntType __y = ((_M_x[__n - 1] & __upper_mask)
| (_M_x[0] & __lower_mask));
_M_x[__n - 1] = (_M_x[__m - 1] ^ (__y >> 1)
^ ((__y & 0x01) ? __a : 0));
_M_p = 0;
}
错误如下:
/usr/include/c++/10/bits/random.tcc:413:33: runtime error: unsigned integer overflow: 397 - 624 cannot be represented in type 'unsigned long'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/include/c++/10/bits/random.tcc:413:33 in
/usr/include/c++/10/bits/random.tcc:413:26: runtime error: unsigned integer overflow: 227 + 18446744073709551389 cannot be represented in type 'unsigned long'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/include/c++/10/bits/random.tcc:413:26 in
看来是有区别的__m-__n == 397 - 624
这显然是负数,但操作数都是无符号的。
被减去的变量是定义为的模板参数size_t __n, size_t __m
所以这不是随机的边缘情况,而是正在实现的实际模板。
这是 STL 实现中的错误还是我的用法错误?
一个最小的可重现示例:https://godbolt.org/z/vvjWscPnj https://godbolt.org/z/vvjWscPnj
更新:向 GCC 提交问题(不是错误)https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106469 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106469- 关闭为“不会修复”
GCC 团队称 clang 的 ubsan 无符号整数溢出检查是不好的做法,因为该行为在 ISO C++ 中是明确定义的(作为模换行)。尽管 PRNG 中使用了模运算,但在本例中并非如此。
然而,在大多数用户空间代码中,未签名的溢出is几乎总是有一个错误需要被捕获,而 GCC 的 STL 上的这个非错误会阻止用户从这项有用的检查中受益。