一、饱和算术
在传统的C++计算或者传统的STL库的数据运算来看,它们都有一个重要的问题,也就是传统的c/c++的运算问题即数据类型的上下限控制,再说的一直白一些,就是数据溢出。如果再往里面分析,其实就是指定的数据类型的内存大小容量无法盛放指定数据类型的大小。
而在这种情况下,传统的计算结果处理为Wrap round或直接溢出导致UB行为。那么这种非常浅显直白的问题,就是一个安全问题或者理解为内存风险的问题。前面不断的说过,C++标准发展的一个重要方向就是使语言从基础上更安全,所以在C++26就提供了解决这类问题的方法,即饱和算术。
饱和算术,Saturation Arithmetic。其实很好理解,就是提供了一系列的库接口,用来自动处理在数据计算时数据溢出的问题,确保结果不会走出数据类型的上下限。
二、饱和算术的说明
目前在C++26的方案中,饱和算术主要支持的还是类整数数据类型如int,char,short,long等类型(整数提升不适合),特别是模板类型参数T不得是(可能带 cv 限定的)bool、char、wchar_t、char8_t、char16_t 和 char32_t类型(这些类型不用于算术运算)。不过饱和算术对浮点等支持还不完善。
在C++26中饱和算术主要的包括以下几个:
1. add_sat:对两个整数执行饱和加法运算
template< class T >
constexpr T add_sat( T x, T y ) noexcept;2. sub_sat:对两个整数执行饱和减法运算
template< class T >
constexpr T sub_sat( T x, T y ) noexcept;3.mul_sat:对两个整数执行饱和乘法运算
template< class T >
constexpr T mul_sat( T x, T y ) noexcept;
4. div_sat:对两个整数执行饱和除法运算
template< class T >
constexpr T div_sat( T x, T y ) noexcept;
5. saturate_cast:返回一个整数值,该值被钳制到另一个整数类型的范围内
template< class T, class U >
constexpr T saturate_cast( U x ) noexcept;
需要说明的是,上面需要传入两个参数的函数,传入的两个参数数据类型不同的情况下会引起编译错误。
三、例程
看一下相关的例程:
#include <cstdint>
#include <limits>
#include <numeric>static_assert
(""&& (std::div_sat<int>(6, 3) == 2) // not saturated&& (std::div_sat<int>(INT_MIN, -1) == INT_MAX) // saturated&& (std::div_sat<unsigned>(6, 3) == 2) // not saturated
);static_assert
(""&& (std::mul_sat<int>(2, 3) == 6) // not saturated&& (std::mul_sat<int>(INT_MAX / 2, 3) == INT_MAX) // saturated&& (std::mul_sat<int>(-2, 3) == -6) // not saturated&& (std::mul_sat<int>(INT_MIN / -2, -3) == INT_MIN) // saturated&& (std::mul_sat<unsigned>(2, 3) == 6) // not saturated&& (std::mul_sat<unsigned>(UINT_MAX / 2, 3) == UINT_MAX) // saturated
);
static_assert
(""&& (std::sub_sat<int>(INT_MIN + 4, 3) == INT_MIN + 1) // not saturated&& (std::sub_sat<int>(INT_MIN + 4, 5) == INT_MIN) // saturated&& (std::sub_sat<int>(INT_MAX - 4, -3) == INT_MAX - 1) // not saturated&& (std::sub_sat<int>(INT_MAX - 4, -5) == INT_MAX) // saturated&& (std::sub_sat<unsigned>(4, 3) == 1) // not saturated&& (std::sub_sat<unsigned>(4, 5) == 0) // saturated
);
int main()
{
//add_satconstexpr int a = std::add_sat(3, 4); // no saturation occurs, T = intstatic_assert(a == 7);constexpr unsigned char b = std::add_sat<unsigned char>(UCHAR_MAX, 4); // saturatedstatic_assert(b == UCHAR_MAX);constexpr unsigned char c = std::add_sat(UCHAR_MAX, 4); // not saturated, T = int// add_sat(int, int) returns int tmp == 259,// then assignment truncates 259 % 256 == 3static_assert(c == 3);// unsigned char d = std::add_sat(252, c); // Error: inconsistent deductions for Tconstexpr unsigned char e = std::add_sat<unsigned char>(251, a); // saturatedstatic_assert(e == UCHAR_MAX);// 251 is of type T = unsigned char, `a` is converted to unsigned char value;// might yield an int -> unsigned char conversion warning for `a`constexpr signed char f = std::add_sat<signed char>(-123, -3); // not saturatedstatic_assert(f == -126);constexpr signed char g = std::add_sat<signed char>(-123, -13); // saturatedstatic_assert(g == std::numeric_limits<signed char>::min()); // g == -128//saturate_castconstexpr std::int16_t x1{696};constexpr std::int8_t x2 = std::saturate_cast<std::int8_t>(x1);static_assert(x2 == std::numeric_limits<std::int8_t>::max());constexpr std::uint8_t x3 = std::saturate_cast<std::uint8_t>(x1);static_assert(x3 == std::numeric_limits<std::uint8_t>::max());constexpr std::int16_t y1{-696};constexpr std::int8_t y2 = std::saturate_cast<std::int8_t>(y1);static_assert(y2 == std::numeric_limits<std::int8_t>::min());constexpr std::uint8_t y3 = std::saturate_cast<std::uint8_t>(y1);static_assert(y3 == 0);return 0;
}
上面的代码是cppreference上的官方代码。
四、总结
通过上面的分析可以发现,C++新标准不断在对其细节上的安全性进行补全或增加相关安全的内容。看来Rust号称安全的宣传给C++的大佬们的压力传导的真不少。照着这个方向发展下去,开发者在应用STL库到工程时,整体代码的安全性会有一个本质的提高。
看来还是有竞争才会有发展,这是不变的道理。