我用c++写了一个程序来寻找ab = C的所有解,其中a, b和C一起使用所有的数字0-9,只使用一次。程序循环遍历a和b的值,并每次对a、b和ab运行数字计数例程,以检查是否满足数字条件。
但是,当ab超出整数限制时,会产生伪解。我最终使用如下代码来检查这个:
unsigned long b, c, c_test;
...
c_test=c*b; // Possible overflow
if (c_test/b != c) {/* There has been an overflow*/}
else c=c_test; // No overflow
是否有更好的方法来测试溢出?我知道有些芯片有一个内部标志,在溢出发生时设置,但我从未见过通过C或c++访问它。
注意,有符号int溢出在C和c++中是未定义的行为,因此您必须在不实际引起它的情况下检测它。对于加法前的有符号整型溢出,请参见在C/ c++中检测有符号溢出。
这里有一个非常快速的方法来检测溢出,至少是加法,这可能会为乘法、除法和乘方提供线索。
其思想是,正是因为处理器会让值归零,而C/ c++是从任何特定的处理器抽象出来的,你可以:
uint32_t x, y;
uint32_t value = x + y;
bool overflow = value < (x | y);
这既确保了如果一个操作数为零,另一个操作数为零,则不会错误地检测到溢出,而且比前面建议的许多NOT/XOR/ and /test操作要快得多。
正如所指出的,这种方法虽然比其他更精细的方法更好,但仍然是可优化的。以下是包含优化的原始代码的修订:
uint32_t x, y;
uint32_t value = x + y;
const bool overflow = value < x; // Alternatively "value < y" should also work
一种更有效、更廉价的检测乘法溢出的方法是:
uint32_t x, y;
const uint32_t a = (x >> 16U) * (y & 0xFFFFU);
const uint32_t b = (x & 0xFFFFU) * (y >> 16U);
const bool overflow = ((x >> 16U) * (y >> 16U)) +
(a >> 16U) + (b >> 16U);
uint32_t value = overflow ? UINT32_MAX : x * y;
这将导致UINT32_MAX溢出,或乘法的结果。在这种情况下,允许对有符号整数进行乘法运算是严格未定义的行为。
值得注意的是,这使用部分Karatsuba方法乘法分解来计算64位乘法的高32位,以检查是否应该设置它们中的任何一个,以了解32位乘法是否溢出。
如果使用c++,你可以把这个转换成一个简洁的小lambda来计算溢出,这样检测器的内部工作就被隐藏了:
uint32_t x, y;
const bool overflow
{
[](const uint32_t x, const uint32_t y) noexcept -> bool
{
const uint32_t a{(x >> 16U) * uint16_t(y)};
const uint32_t b{uint16_t(x) * (y >> 16U)};
return ((x >> 16U) * (y >> 16U)) + (a >> 16U) + (b >> 16U);
}(x, y)
};
uint32_t value{overflow ? UINT32_MAX : x * y};
对于无符号整数,只需检查结果是否小于其中一个参数:
unsigned int r, a, b;
r = a + b;
if (r < a)
{
// Overflow
}
对于有符号整数,可以检查参数和结果的符号。
不同符号的整数不能溢出,相同符号的整数只有在结果为不同符号时才会溢出:
signed int r, a, b, s;
r = a + b;
s = a>=0;
if (s == (b>=0) && s != (r>=0))
{
// Overflow
}
警告:GCC在使用-O2编译时会优化掉溢出检查。
选项-Wall会在某些情况下给你一个警告
if (a + b < a) { /* Deal with overflow */ }
但在这个例子中不是:
b = abs(a);
if (b < 0) { /* Deal with overflow */ }
唯一安全的方法是在溢出发生之前检查溢出,正如CERT论文中所描述的那样,系统地使用这种方法将非常繁琐。
使用-fwrapv编译可以解决这个问题,但会禁用一些优化。
我们迫切需要一个更好的解决方案。我认为编译器应该发出一个警告,默认情况下,优化依赖于溢出没有发生。目前的情况允许编译器优化掉溢出检查,这在我看来是不可接受的。
我看到你用的是无符号整数。根据定义,在C中(我不了解c++),无符号算术不会溢出…所以,至少对C来说,你的观点是没有意义的:)
对于有符号整数,一旦出现溢出,就会发生未定义行为(UB),程序可以做任何事情(例如:使测试不确定)。
#include <limits.h>
int a = <something>;
int x = <something>;
a += x; /* UB */
if (a < 0) { /* Unreliable test */
/* ... */
}
要创建一个符合要求的程序,您需要在生成溢出之前测试溢出。该方法也可以用于无符号整数:
// For addition
#include <limits.h>
int a = <something>;
int x = <something>;
if (x > 0 && a > INT_MAX - x) // `a + x` would overflow
if (x < 0 && a < INT_MIN - x) // `a + x` would underflow
// For subtraction
#include <limits.h>
int a = <something>;
int x = <something>;
if (x < 0 && a > INT_MAX + x) // `a - x` would overflow
if (x > 0 && a < INT_MIN + x) // `a - x` would underflow
// For multiplication
#include <limits.h>
int a = <something>;
int x = <something>;
// There may be a need to check for -1 for two's complement machines.
// If one number is -1 and another is INT_MIN, multiplying them we get abs(INT_MIN) which is 1 higher than INT_MAX
if (a == -1 && x == INT_MIN) // `a * x` can overflow
if (x == -1 && a == INT_MIN) // `a * x` (or `a / x`) can overflow
// general case
if (x != 0 && a > INT_MAX / x) // `a * x` would overflow
if (x != 0 && a < INT_MIN / x) // `a * x` would underflow
对于除法(INT_MIN和-1特殊情况除外),不可能超过INT_MIN或INT_MAX。