如何在C语言中检测整数溢出

26

我们知道CPython在数字变得更大时会自动将整数提升为长整型(允许任意精度算术)。

在纯C中,我们如何检测intlong long的溢出?


4
由于存在有符号整数算术溢出等问题,仅仅添加两个数字并检查其值是否超过某个阈值非常棘手。一个简单的解决方案可能是检查要检查的值‘x’是否高于特定阈值,或者将其加1后是否超过阈值。如果它确实超过了阈值且您希望添加的另一个数字大于1,则表示发生了溢出情况。 - Some programmer dude
2
挑剔一下,但是实际上是 CPython 2.7 这样做的。CPython 3 没有“升级”任何东西,即使在内部也只有一种类型。 - Antti Haapala -- Слава Україні
1
有很多重复的内容,具体取决于您想对这些值进行什么操作(加/减/乘/除/...?)如何检查A+B是否超过long long?(A和B都是long long类型)在C/C++中检测有符号溢出测试整数加法是否溢出 - phuclv
检测int溢出的更简单方法 - NoChance
3个回答

32

你无法检测有符号的 int 溢出。你必须编写代码来避免它。

有符号的 int 溢出是 未定义行为,如果在程序中存在,程序就是无效的,编译器不需要生成任何特定的行为。


5
在进行计算之前,您可以检查输入值以防止溢出。 - A.R.C.
7
解释有符号整数溢出为什么是未定义的,而无符号整数似乎不是,这将有助于增加解释性和信息量。 - hetepeperfan
8
因为这是语言标准所规定的。 - Sneftel
6
@sneftel的论点虽然很有说服力,但缺乏权威来源支持,尽管它可能是正确的。此外,一旦人们开始理解语言,标准对他们来说会更有意义,这也许是他们首先访问stackoverflow的原因之一。 - hetepeperfan
5
@hetepeperfan,标准书写的原因大多数情况下超出了Stack Overflow的范围。 - Antti Haapala -- Слава Україні
显示剩余3条评论

30

在进行有符号加法运算之前,您必须检查可能的溢出。尝试在求和后检测它是没有用的。您可以预测 signed int overflow,但在求和后尝试检测它已经太晚了。

通过在求和后测试来避免未定义行为是不可能的。如果加法溢出,那么已经存在未定义行为。

如果是我的话,我会这样做:

#include <limits.h>

int safe_add(int a, int b) 
{
    if (a >= 0) {
        if (b > (INT_MAX - a)) {
            /* handle overflow */
        }
    } else {
        if (b < (INT_MIN - a)) {
            /* handle underflow */
        }
    }
    return a + b;
}

请参考这篇论文以获取更多信息。在同一篇论文中,您还可以找到为什么无符号整数溢出不是未定义的行为以及可能存在的可移植性问题。

编辑:

GCC和其他编译器有一些规定来检测溢出。例如,GCC具有以下内置函数,允许执行简单的算术运算,并检查是否溢出。

bool __builtin_add_overflow (type1 a, type2 b, type3 *res)
bool __builtin_sadd_overflow (int a, int b, int *res)
bool __builtin_saddl_overflow (long int a, long int b, long int *res)
bool __builtin_saddll_overflow (long long int a, long long int b, long long int *res)
bool __builtin_uadd_overflow (unsigned int a, unsigned int b, unsigned int *res)
bool __builtin_uaddl_overflow (unsigned long int a, unsigned long int b, unsigned long int *res)
bool __builtin_uaddll_overflow (unsigned long long int a, unsigned long long int b, unsigned long long int *res)

访问此链接

编辑:

关于某人提出的问题:

我认为,解释为什么带符号整数溢出未定义而无符号整数明显不是一个好主意,这将很好地解释并提供信息。

答案取决于编译器的实现。大多数C实现(编译器)只是使用最容易实现的整数表示的溢出行为。

实际上,表示有符号值的方法可能不同(根据实现): 补码, 反码加1, 原码。对于无符号类型,标准没有理由允许变化,因为只有一种明显的二进制表示(标准只允许二进制表示)。


9
两者技术上都被称为溢出。下溢意味着数值在大小上过小,无法在浮点变量中表示。 - Antti Haapala -- Слава Україні
@chqrlie,但是你的更改是不正确的,因为它忽略了测试a == 0,这是隐含在那里的,即没有保存任何东西。 - Antti Haapala -- Слава Україні
3
它并没有忽略情况 a == 0,即没有可能发生溢出的情况,只是以不同的方式处理。 - chqrlie
如果将有符号整数强制转换为无符号整数会怎样呢?虽然溢出结果是未定义的,但无符号整数的溢出是被定义的。因此,要测试a+b是否溢出,请测试(uint)a + (uint)b < (uint)a。将int强制转换为uint是明确定义的,所有其他操作也是如此。编译器可能能够在0条指令内完成数字的转换。 - Eyal
请注意,仅当a小于0时才会执行INT_MIN - a,这意味着结果将是INT_MIN加上最多为INT_MAX的数字。因此,结果将介于INT_MIN0之间,因此不会发生溢出。 - Eyal
显示剩余6条评论

11

在执行加法之前,必须测试带符号的操作数。这里是一个安全的加法函数,在所有情况下都进行了2次比较:

#include <limits.h>

int safe_add(int a, int b) {
    if (a >= 0) {
        if (b > INT_MAX - a) {
            /* handle overflow */
        } else {
            return a + b;
        }
    } else {
        if (b < INT_MIN - a) {
            /* handle negative overflow */
        } else {
            return a + b;
        }
    }
}
如果已知类型long long的范围比类型int大,您可以使用这种方法,可能会更快:
#include <limits.h>

int safe_add(int a, int b) {
    long long res = (long long)a + b;
    if (res > INT_MAX || res < INT_MIN) {
        /* handle overflow */
    } else {
        return (int)res;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接