我用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中捕获整数溢出指出了一种比CERT讨论的更通用的解决方案(就处理的类型而言,它更通用),即使它需要一些GCC扩展(我不知道它们有多广泛的支持)。

其他回答

从C23开始,标准头文件<stdckdint.h>提供了以下三个类函数宏:

bool ckd_add(type1 *result, type2 a, type3 b);
bool ckd_sub(type1 *result, type2 a, type3 b);
bool ckd_mul(type1 *result, type2 a, type3 b);

其中type1, type2和type3是任何整数类型。这些函数分别以任意精度对a和b进行加、减或乘,并将结果存储在*result中。如果结果不能由type1精确表示,则函数返回true("计算已溢出")。(任意精确是一种错觉;计算速度非常快,自20世纪90年代初以来几乎所有可用的硬件都可以在一个或两个指令内完成。)

重写OP的例子:

unsigned long b, c, c_test;
// ...
if (ckd_mul(&c_test, c, b))
{
    // returned non-zero: there has been an overflow
}
else
{
    c = c_test; // returned 0: no overflow
}

C_test包含所有情况下可能溢出的乘法结果。

早在C23之前,GCC 5+和Clang 3.8+就提供了以同样方式工作的内置程序,除了结果指针是最后传递而不是第一个传递:__builtin_add_overflow, __builtin_sub_overflow和__builtin_mul_overflow。这些也适用于小于int的类型。

unsigned long b, c, c_test;
// ...
if (__builtin_mul_overflow(c, b, &c_test))
{
    // returned non-zero: there has been an overflow
}
else
{
    c = c_test; // returned 0: no overflow
}

Clang 3.4+引入了具有固定类型的算术溢出内置函数,但它们的灵活性要低得多,而且Clang 3.8现在已经可用很长时间了。如果你需要使用__builtin_umull_overflow,尽管有更方便的更新选项。

Visual Studio的cl.exe没有直接的等价物。对于无符号加减法,包括<intrin.h>将允许您使用addcarry_uNN和subborrow_uNN(其中NN是位数,如addcarry_u8或subborrow_u64)。他们的签名有点迟钝:

unsigned char _addcarry_u32(unsigned char c_in, unsigned int src1, unsigned int src2, unsigned int *sum);
unsigned char _subborrow_u32(unsigned char b_in, unsigned int src1, unsigned int src2, unsigned int *diff);

C_in /b_in是输入的进位/借位标志,返回值是输出的进位/借位。它似乎没有符号运算或乘法的等价物。

另外,Clang for Windows现在已经可以投入生产(对于Chrome来说已经足够好了),所以这也是一个选择。

警告:GCC在使用-O2编译时会优化掉溢出检查。 选项-Wall会在某些情况下给你一个警告

if (a + b < a) { /* Deal with overflow */ }

但在这个例子中不是:

b = abs(a);
if (b < 0) { /* Deal with overflow */ }

唯一安全的方法是在溢出发生之前检查溢出,正如CERT论文中所描述的那样,系统地使用这种方法将非常繁琐。

使用-fwrapv编译可以解决这个问题,但会禁用一些优化。

我们迫切需要一个更好的解决方案。我认为编译器应该发出一个警告,默认情况下,优化依赖于溢出没有发生。目前的情况允许编译器优化掉溢出检查,这在我看来是不可接受的。

如果您有一个比您想要测试的数据类型大的数据类型(比如您做了一个32位的添加,而您有一个64位的类型),那么这将检测是否发生溢出。我的例子是一个8位的添加。但它可以放大。

uint8_t x, y;    /* Give these values */
const uint16_t data16    = x + y;
const bool carry        = (data16 > 0xFF);
const bool overflow     = ((~(x ^ y)) & (x ^ data16) & 0x80);

它基于本页解释的概念:http://www.cs.umd.edu/class/spring2003/cmsc311/Notes/Comb/overflow.html

对于一个32位的例子,0xFF变成0xFFFFFFFF, 0x80变成0x80000000,最后uint16_t变成uint64_t。

注意:这捕获整数加法/减法溢出,我意识到你的问题涉及乘法。在这种情况下,分裂可能是最好的办法。这通常是calloc实现确保参数在相乘以获得最终大小时不会溢出的一种方式。

另一个有趣的工具是IOC: C/ c++的整数溢出检查器。

这是一个修补过的Clang编译器,它在编译时向代码添加检查。

输出如下所示:

CLANG ARITHMETIC UNDEFINED at <add.c, (9:11)> :
Op: +, Reason : Signed Addition Overflow,
BINARY OPERATION: left (int32): 2147483647 right (int32): 1

一些编译器提供了对CPU中整数溢出标志的访问,然后可以测试,但这不是标准的。

您还可以在执行乘法之前测试溢出的可能性:

if ( b > ULONG_MAX / a ) // a * b would overflow