假设您正在使用<cstdint>
以及类似std::uint8_t
和std::uint16_t
的类型,并希望对其执行+=
和*=
等操作。你喜欢对这些数字进行算术运算,就像在C / C ++中一样。这通常有效,您可以通过实验找到std::uint8_t
,std::uint32_t
和std::uint64_t
,但不是std::uint16_t
。
具体而言,与std::uint16_t
的乘法有时会失败,优化的构建会产生各种奇怪的结果。原因?由于有符号整数溢出导致的未定义行为。编译器基于未发生未定义行为的假设进行优化,因此开始从程序中修剪代码块。具体的未定义行为如下:
std::uint16_t x = UINT16_C(0xFFFF);
x *= x;
原因在于C ++的推广规则以及您和其他几乎所有人一样使用std::numeric_limits<int>::digits == 31
平台的事实。也就是说,int
是32位(digits
计数位而不是符号位)。 x
被提升为signed int
,尽管是无符号的,并且0xFFFF * 0xFFFF
溢出了32位带符号算术。
一般问题的演示:
// Compile on a recent version of clang and run it:
// clang++ -std=c++11 -O3 -Wall -fsanitize=undefined stdint16.cpp -o stdint16
#include <cinttypes>
#include <cstdint>
#include <cstdio>
int main()
{
std::uint8_t a = UINT8_MAX; a *= a; // OK
std::uint16_t b = UINT16_MAX; b *= b; // undefined!
std::uint32_t c = UINT32_MAX; c *= c; // OK
std::uint64_t d = UINT64_MAX; d *= d; // OK
std::printf("%02" PRIX8 " %04" PRIX16 " %08" PRIX32 " %016" PRIX64 "\n",
a, b, c, d);
return 0;
}
你会得到一个很好的错误:
main.cpp:11:55: runtime error: signed integer overflow: 65535 * 65535
cannot be represented in type 'int'
当然,避免这种情况的方法是在乘法之前至少投射unsigned int
。只有无符号类型的位数恰好等于int
的位数的一半的确切情况才有问题。任何较小的都会导致乘法无法溢出,就像使用std::uint8_t
一样;任何较大的类型都会导致类型完全映射到其中一个促销排名,例如std::uint64_t
匹配unsigned long
或unsigned long long
,具体取决于平台。
但这真的很糟糕:它需要根据当前平台上int
的大小知道哪种类型存在问题。有没有更好的方法可以在没有#if
迷宫的情况下避免使用无符号整数乘法的未定义行为?
答案 0 :(得分:9)
可能使用SFINAE进行一些模板元编程。
#include <type_traits>
template <typename T, typename std::enable_if<std::is_unsigned<T>::value && (sizeof(T) <= sizeof(unsigned int)) , int>::type = 0>
T safe_multiply(T a, T b) {
return (unsigned int)a * (unsigned int)b;
}
template <typename T, typename std::enable_if<std::is_unsigned<T>::value && (sizeof(T) > sizeof(unsigned int)) , int>::type = 0>
T safe_multiply(T a, T b) {
return a * b;
}
修改:更简单:
template <typename T, typename std::enable_if<std::is_unsigned<T>::value, int>::type = 0>
T safe_multiply(T a, T b) {
typedef typename std::make_unsigned<decltype(+a)>::type typ;
return (typ)a * (typ)b;
}
答案 1 :(得分:7)
这是一个相对简单的解决方案,对于无符号类型比unsigned int
更窄,强制促销为int
而不是int
。我不认为任何代码是由promote
生成的,或者至少没有代码比标准整数提升生成的代码;它只会强制乘法等使用无符号运算而不是有符号运算:
#include <type_traits>
// Promote to unsigned if standard arithmetic promotion loses unsignedness
template<typename integer>
using promoted =
typename std::conditional<std::numeric_limits<decltype(integer() + 0)>::is_signed,
unsigned,
integer>::type;
// function for template deduction
template<typename integer>
constexpr promoted<integer> promote(integer x) { return x; }
// Quick test
#include <cstdint>
#include <iostream>
#include <limits>
int main() {
uint8_t i8 = std::numeric_limits<uint8_t>::max();
uint16_t i16 = std::numeric_limits<uint16_t>::max();
uint32_t i32 = std::numeric_limits<uint32_t>::max();
uint64_t i64 = std::numeric_limits<uint64_t>::max();
i8 *= promote(i8);
i16 *= promote(i16);
i32 *= promote(i32);
i64 *= promote(i64);
std::cout << " 8: " << static_cast<int>(i8) << std::endl
<< "16: " << i16 << std::endl
<< "32: " << i32 << std::endl
<< "64: " << i64 << std::endl;
return 0;
}
答案 2 :(得分:6)
关于在select a.id, a.log_at, max(b.log_at), count(1)
from logs a
join logs b on b.log_at >= a.log_at and b.log_at <= a.log_at+ '20 m'::interval
group by 1, 2
having count(1) > 1
order by 1
为64位的系统上uint32_t * uint32_t
乘法的情况的C解决方案的文章有一个我没想过的非常简单的解决方案:32 bit unsigned multiply on 64 bit causing undefined behavior?
转换为我的问题的解决方案很简单:
int
简单地将static_cast<std::uint16_t>(1U * x * x)
放在算术运算链的左侧会将第一个参数提升到更大的1U
和unsigned int
等级,然后依此类推。促销将确保答案既未签名且请求的位仍然存在。然后最终的演员表将其缩减回所需的类型。
这简单而优雅,我希望一年前我能想到它。感谢所有回复的人。