如何检测 C 中的整数溢出
How to detect integer overflow in C
我们知道,当数字变大时,CPython 会默默地将整数提升为长整数(允许任意精度算术)。
我们如何在纯 C 中检测 int
和 long long
的溢出?
您无法检测 signed int
溢出。您必须编写代码以避免它。
Signed int 溢出是未定义行为,如果它出现在您的程序中,则该程序无效,编译器不需要生成任何特定行为。
您可以预测 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;
}
有关详细信息,请参阅此 paper。您还可以在同一篇论文中找到为什么无符号整数溢出不是未定义行为以及什么可能是可移植性问题。
编辑:
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)
访问此 link。
编辑:
关于某人提出的问题
I think, it would be nice and informative to explain why signed int overflow undefined, whereas unsigned apperantly isn't..
答案取决于编译器的实现。大多数 C 实现(编译器)只使用最容易用它使用的整数表示实现的任何溢出行为。
在实践中,有符号值的表示可能不同(根据实现):one's complement
、two's complement
、sign-magnitude
。对于无符号类型,标准没有理由允许变化,因为只有一个明显的 binary representation
(标准只允许二进制表示)。
在执行加法之前必须测试有符号操作数。这是一个在所有情况下都有 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;
}
}
我们知道,当数字变大时,CPython 会默默地将整数提升为长整数(允许任意精度算术)。
我们如何在纯 C 中检测 int
和 long long
的溢出?
您无法检测 signed int
溢出。您必须编写代码以避免它。
Signed int 溢出是未定义行为,如果它出现在您的程序中,则该程序无效,编译器不需要生成任何特定行为。
您可以预测 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;
}
有关详细信息,请参阅此 paper。您还可以在同一篇论文中找到为什么无符号整数溢出不是未定义行为以及什么可能是可移植性问题。
编辑:
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)
访问此 link。
编辑:
关于某人提出的问题
I think, it would be nice and informative to explain why signed int overflow undefined, whereas unsigned apperantly isn't..
答案取决于编译器的实现。大多数 C 实现(编译器)只使用最容易用它使用的整数表示实现的任何溢出行为。
在实践中,有符号值的表示可能不同(根据实现):one's complement
、two's complement
、sign-magnitude
。对于无符号类型,标准没有理由允许变化,因为只有一个明显的 binary representation
(标准只允许二进制表示)。
在执行加法之前必须测试有符号操作数。这是一个在所有情况下都有 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;
}
}