要检测C中的int上溢/下溢,我使用此代码。

假设2的补码,不要使用更宽的整数。

int a,b,sum;
sum = a + b;
// out-of-range only possible when the signs are the same.
if ((a < 0) == (b < 0)) {
  if (a < 0) {
    // Underflow here means the result is excessively negative.
    if (sum > b) UnderflowDetected();
  }
  else {
    if (sum < b) OverflowDetected();  
  }

/>

评论

我认为您误解了下溢...还是我?举例来说,浮点数可以表示的最小数字为0.001。 1.0 / 10000会导致值为0.0,因为实际值太小。

@BitFiddlingCodeMonkey,它在整数上-在各种环绕情况下,加法的结果不适合相同大小的整数。有时它是两个负数之和不适合时的下溢。

如果您只是从使用int更改为使用unsigned int,或者更好的是使用uint32_t和size_t,则可以在操作后进行这些检查。对于带符号的int,由于未定义的行为,因此事后无法检测到上溢和下溢。并警告:未定义的行为可能会表现为任何行为,从程序正常运行到安装在计算机上并用于窃取信用卡信息的恶意软件。

@chux:否-我只是指出这里使用的方法(即进行加法然后查看是否发生溢出)仅对无符号整数有效。对于有符号整数,它永远是无效的,因为有符号整数的溢出本质上是在语言中未定义的。

从GCC 5开始,有内置函数可以执行此操作。

#1 楼

事后测试无法避免未定义的行为!如果添加溢出,则这里已经存在未定义的行为:

sum = a + b;


,因此尝试进行后续测试为时已晚。在进行签名添加之前,必须测试可能的溢出。 (如果对此感到困惑,请阅读Dietz等人(2012年),“了解C / C ++中的整数溢出”。甚至您也不会感到困惑:这是一篇很棒的论文!)

如果是我,我会做这样的事情:

#include <limits.h>

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


,但是我不完全确定将上溢和下溢分开处理的意义是什么。

在进行测试时,我也使用Clang的-fsanitize=undefined

评论


\ $ \ begingroup \ $
C99标准中的第5.2.4.2.1节将INT_MIN定义为“用于int类型的对象的最小值”,将INT_MAX定义为“用于int类型的对象的最大值”。
\ $ \ endgroup \ $
–加雷斯·里斯(Gareth Rees)
2013年12月11日23:20

\ $ \ begingroup \ $
是的,是的。典型值为INT_MIN = −2 ^ 32 = −2147483648和INT_MAX = 2 ^ 32 − 1 = 2147483647,但是这些值可能会因处理器和编译器而异,因此使代码可移植的唯一方法是使用宏。
\ $ \ endgroup \ $
–加雷斯·里斯(Gareth Rees)
2013年12月11日23:27



\ $ \ begingroup \ $
@Jamal实际上,带有。准确地说,是std :: numeric_limits :: min()和std :: numeric_limits :: max()。
\ $ \ endgroup \ $
–玉石
2013年12月12日下午0:16

\ $ \ begingroup \ $
值得指出的是,整数上溢和下溢仅对于SIGNED类型是未定义的。对于无符号整数,溢出作为模算术安全地发生。
\ $ \ endgroup \ $
–马特
2013年12月12日在12:02

\ $ \ begingroup \ $
@Max:如果+ b溢出,则将具有未定义的行为,而我们试图避免的是未定义的行为! (但是,即使定义了+ b,您的表达式也无法使用,因为a + b是一个int,并且int的所有值都小于或等于INT_MAX。)
\ $ \ endgroup \ $
–加雷斯·里斯(Gareth Rees)
13年12月13日在13:36

#2 楼


检测int溢出的更简单方法...


我知道的两个最简单的方法是:


在C ++
使用C中的safe_iop库

SafeInt由David LeBlanc编写,Microsoft则使用它。 safe_iop由???编写,并且Android使用它。


下一个最简单的方法是使用编译器固有函数。不幸的是,我没有看到很多。我相信我最近在GCC上看到了一些。

关于内在函数的整洁事情是(1)它们提供了熟悉的C函数调用,(2)它们不受您要避免的Undefined Behavior的束缚。 。这意味着一个内在的人可以执行加法,即使溢出它也仍然可以很好地定义该程序。

(在C / C ++中,如果执行加法并且它溢出,则该程序是非法的。不允许执行该操作,然后检查结果。)


下一个最简单的方法是汇编和内联汇编。同样,它不受您要在C / C ++中避免的未定义行为的约束。

组装和内联组装例程是我使用的方法。我在移动平台上工作,我有一个用于i686,x86_64,ARM和MIPS的库。

我很久以前就了解到,尝试在一个井中进行跨平台操作很麻烦。 C定义,可移植且高效的方式,尤其是对于某些操作。

我一直在检查编译结果并盯着反汇编的代码,以确保代码生成良好。因此,我以简单和高效的名义放弃了可移植性。


另请参阅如何在C / C ++中检测整数溢出?在堆栈溢出上。

评论


\ $ \ begingroup \ $
截至2018年末,这里有一个与SafeInt非常相似的Boost库:boost.org/doc/libs/1_74_0/libs/safe_numerics/doc/html/tutorial/…
\ $ \ endgroup \ $
– Max Barraclough
20 Sep 24 '10:17

#3 楼

在两种情况下会发生上溢和下溢:



两个负数的总和变为正或
两个正数的总和为负。

然后,您可以使用以下逻辑表达式:

     ((a<0)&&(b<0)&&(a+b>0)) || ((a>0)&&(b>0)&&(a+b<0))


,或者如果您更喜欢整数算术而不是逻辑表达式:

     (a<0)*(b<0)*(a+b>0) + (a>0)*(b>0)*(a+b<0)


在两个补码中,一个可以很简单,只需挑出符号位即可在甚至硬件上进行操作。

评论


\ $ \ begingroup \ $
考虑到您要调用“未定义行为”,然后再尝试对其进行测试,因此这并不是完全可移植的。例如,该解决方案在具有饱和算法的系统中失败。
\ $ \ endgroup \ $
– Toby Speight
17年9月19日在10:04

\ $ \ begingroup \ $
@TobySpeight:是的,您是对的。我想我对那些饱和的算法还不够用,无法自动考虑。我们可以从一开始就进行一些恒定的编译时间测试来确定该问题,并使用宏来决定是编译我们的代码还是饱和代码。
\ $ \ endgroup \ $
–宏线程
17-09-19在10:29



#4 楼

为什么不使用long来保存计算结果?然后可以对照(int)MAX和MIN值检查long类型,以查看是否发生上溢或下溢?如果没有发生冲突,则可以安全地将结果重新发送到(int)。

或者,这是否太简单了,而我遗漏了一些非常基础的东西?我要忽略的一件事是,长整型也可能溢出。

评论


\ $ \ begingroup \ $
由于long类型只能保证至少为int的范围,因此转换为long可能不会提供任何其他范围。因此,问题与int基本上一样。同样的情况长久了。
\ $ \ endgroup \ $
–chux-恢复莫妮卡
14年7月6日在17:18



\ $ \ begingroup \ $
尽管从技术上讲,long long只能保证至少与int一样长,但我不知道任何平台实际上使用与int相同的字节数来实现long long。因此,在实践中,此解决方案就可以了,而且简单得多。
\ $ \ endgroup \ $
– Samuel Li
19 Mar 25 '19在5:29

\ $ \ begingroup \ $
@SamuelLi仅仅因为它有效并不意味着它是可移植的。
\ $ \ endgroup \ $
–S.S. Anne
20年1月22日在12:18

\ $ \ begingroup \ $
@SSAnne虽然您的说法是正确的,但是由于那里只有少数平台,并且在不久的将来还没有出现,所以我想说这是当今和未来很多年内可用平台的便携式解决方案。 。
\ $ \ endgroup \ $
– Samuel Li
20年1月23日15:53

\ $ \ begingroup \ $
@SamuelLi x86_64的ILP64 ABI具有64位int,long和long long。
\ $ \ endgroup \ $
–S.S. Anne
20年1月23日在16:14