通常情况下,我如何在C语言中防止整数溢出?

7

一般来说,在C编程语言中,我该如何避免整数溢出?我的意思是,是否有任何函数可以预防它发生?最后,整数溢出会像缓冲区溢出那样使我被黑客攻击吗?


3
前往观看甘地在游戏文明中引发核爆炸的故障案例,其中整数溢出导致了混乱和乐趣。https://www.youtube.com/watch?v=YOg-V4OBZc0 - Michael Dorgan
你可以实现饱和算术函数(类似于这个 无符号饱和加法)并在任何可能导致问题的溢出代码中使用它们。 - Felix G
请查看大数算术库。它们的速度比标准 C 算术慢一些,但至少您不会遇到溢出问题。此外,许多标准数学函数(例如 exp)在溢出时将 errno 设置为 ERANGE。 - ashvatthama
通常情况下,编写代码时仔细思考可以避免此类情况。但有时候即使这样做了,仍然会发生错误。 - user253751
6个回答

7
假设你有两个 int 类型的数 a 和 b,你想检查a+b是否会产生溢出或下溢。
有两种情况:a ≥ 0 和 a ≤ 0。在第一种情况下,你不可能发生下溢。如果 b > INT_MAX - a,则会发生溢出。在第二种情况下,你不可能发生溢出。如果 b < INT_MIN - a,则会发生下溢。可以使用以下单个表达式:
a >= 0 ? b > INT_MAX - a : b < INT_MIN - a

2

在操作之前检查结果是否会溢出。

gcc提供了一些辅助内置函数。

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

    These built-in functions promote the first two operands into infinite precision signed type and perform addition on those promoted operands. The result is then cast to the type the third pointer argument points to and stored there. If the stored result is equal to the infinite precision result, the built-in functions return false, otherwise they return true. As the addition is performed in infinite signed precision, these built-in functions have fully defined behavior for all argument values.

    The first built-in function allows arbitrary integral types for operands and the result type must be pointer to some integral type other than enumerated or boolean type, the rest of the built-in functions have explicit integer types.

    The compiler will attempt to use hardware instructions to implement these built-in functions where possible, like conditional jump on overflow after addition, conditional jump on carry etc.

Built-in Function: bool __builtin_sub_overflow (type1 a, type2 b, type3 *res)
Built-in Function: bool __builtin_ssub_overflow (int a, int b, int *res)
Built-in Function: bool __builtin_ssubl_overflow (long int a, long int b, long int *res)
Built-in Function: bool __builtin_ssubll_overflow (long long int a, long long int b, long long int *res)
Built-in Function: bool __builtin_usub_overflow (unsigned int a, unsigned int b, unsigned int *res)
Built-in Function: bool __builtin_usubl_overflow (unsigned long int a, unsigned long int b, unsigned long int *res)
Built-in Function: bool __builtin_usubll_overflow (unsigned long long int a, unsigned long long int b, unsigned long long int *res)

    These built-in functions are similar to the add overflow checking built-in functions above, except they perform subtraction, subtract the second argument from the first one, instead of addition.

Built-in Function: bool __builtin_mul_overflow (type1 a, type2 b, type3 *res)
Built-in Function: bool __builtin_smul_overflow (int a, int b, int *res)
Built-in Function: bool __builtin_smull_overflow (long int a, long int b, long int *res)
Built-in Function: bool __builtin_smulll_overflow (long long int a, long long int b, long long int *res)
Built-in Function: bool __builtin_umul_overflow (unsigned int a, unsigned int b, unsigned int *res)
Built-in Function: bool __builtin_umull_overflow (unsigned long int a, unsigned long int b, unsigned long int *res)
Built-in Function: bool __builtin_umulll_overflow (unsigned long long int a, unsigned long long int b, unsigned long long int *res)

    These built-in functions are similar to the add overflow checking built-in functions above, except they perform multiplication, instead of addition.

The following built-in functions allow checking if simple arithmetic operation would overflow.

Built-in Function: bool __builtin_add_overflow_p (type1 a, type2 b, type3 c)
Built-in Function: bool __builtin_sub_overflow_p (type1 a, type2 b, type3 c)
Built-in Function: bool __builtin_mul_overflow_p (type1 a, type2 b, type3 c)

整数溢出本身就是未定义行为,可能会导致许多问题。但这取决于您的代码。如果它不是指针算术或数组索引的一部分,那么您将不会被“黑客攻击”。


使用通用的 type1,type2,type3 版本与具有显式参数类型的版本之间是否存在性能差异? - Emile Cormier
1
@EmileCormier 它通常会生成与 gcc 端口的作者能够实现的特定类型对应的有效代码。 - 0___________

1
每当你声明一个整数变量时:
  1. 要考虑它会包含多大/小的数字。
  2. 要考虑是否需要有符号或无符号。无符号通常较少问题。
  3. 从stdint.h中选择最小的intn_t或uintn_t类型,以满足上述需求(或者如果你愿意,可以选择...fast_t等类型)。
  4. 如果需要,可以设计包含变量将保存的最大和/或最小值的整数常量,并在进行算术运算时检查这些常量。
也就是说,在代码中不要盲目地滥用int而不加思索。
有符号类型可能会带来除溢出之外的其他问题,尤其是在进行按位运算时。为避免溢出、下溢和意外的有符号按位运算,还需要了解各种隐式整数类型提升规则。

整数溢出会像缓冲区溢出一样让我被黑客攻击吗?

不完全是,但如果有人意识到它,任何错误都可以被利用 - 正如你在几乎每个电脑游戏中看到的那样。


0

1
目前你的回答不够清晰。请编辑并添加更多细节,以帮助其他人理解它如何回答所提出的问题。你可以在帮助中心找到有关如何撰写好答案的更多信息。 - Community

0

如果您想确保不会发生溢出,但可以接受一定的性能成本,请考虑使用任意精度算术库(例如 tiny-bignum-cGNU Multiple Precision Arithmetic)。

自 C99 以来:标准库的数学函数通过设置 errno = ERANGE 来报告溢出,因此您可以检查它(在调用该数学函数之前记得设置 errno = 0)。math.h 定义了 HUGE_VAL 和相关的宏,您可以检查是否发生了溢出,对于浮点数错误,则有 fenv 函数族,有关 math_error(7)fenv(3) 的手册请参阅相关说明。


标准库的数学函数大多提供浮点数计算,与整数溢出的问题并不特别相关。 - John Bollinger
@JohnBollinger 是的,但我认为如果OP在询问如何防止整数溢出,他们也会对浮点溢出感兴趣。 - ashvatthama

-1

你无法完全防止整数溢出。如果发生了,就会发生。你需要在编码时非常小心。

但是,在赋值之前,你可以尝试检查是否可能发生溢出。

对于赋值给类型为intlong int的对象的情况,一种可移植的检查整数溢出的方法是首先将该值分配给类型为long long int的对象。

然后比较存储的值是否大于INT_MAX或小于INT_MIN(对于int)或大于LONG_MAX或小于LONG_MIN(对于long int)。

如果是,你就知道它超出了范围,这样就可以防止整数溢出。

如果你想使用函数而不是直接调用代码,你可以将这个技巧封装到自己的自定义函数中,例如:

#include <limits.h>

// For int.

_Bool INT_OF_CHECK (long long int n)
{
    return ( n > INT_MAX || n < INT_MIN ) ? 1 : 0;  
}

// For long int.

_Bool LINT_OF_CHECK (long long int n)
{
    return ( n > LONG_MAX || n < LONG_MIN ) ? 1 : 0;  
}

该函数返回1,如果值不符合范围,返回0,如果符合范围。
不幸的是,这种检查方法对于long long int本身的赋值并不起作用,但对于intlong int可能有所帮助。
它的缺点在于它不能覆盖这样一种情况,即要分配的值比long long int可以容纳的范围更大或更小,而溢出会在此处发生,但它仅仅是一种可能的方法。
你可以在头文件limits.h中找到宏。

1
这些函数并没有太多意义。例如,在 INT_OF_CHECK 的情况下,如果将大于 LLONG_MAX 或小于 LLONG_MIN 的值分配给 int,会发生什么?此外,在实际进行算术运算(例如 x * y)时,整数溢出经常发生,而不是在赋值时。而无符号整数根本不会溢出。 - P.P
“例如,在 INT_OF_CHECK 的情况下,如果将大于 LLONG_MAX 或小于 LLONG_MIN 的值分配给 int 型变量会怎样?” - 是的,但我已经在上一段中承认了这一点。 - RobertS supports Monica Cellio
此外,在实际操作中进行算术运算(例如 x * y)时,整数溢出经常发生,而不是在赋值时发生。但这只是一种证明溢出可能发生的方法。还可以使用算术表达式作为函数参数并进行检查。正如我已经说过的那样,它并不能涵盖所有情况,但比没有好,人们可以使用这些函数来检查是否可能发生溢出(要求值为最大 LLONG_MAX 和最小 LLONG_MIN)。 - RobertS supports Monica Cellio
移除了 unsigned 方法。 - RobertS supports Monica Cellio

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