如何检测无符号整数乘法溢出?


618

我在C ++编写一个程序来找到所有的解决方案b = c ^,其中一个bc ^一起使用所有的数字0-9只出现一次。该程序循环了ab的值,并且每次在aba b上运行一个数字计数例程,以检查是否满足数字条件。

然而,当可以产生伪解一个b溢出整数限制。我最终使用如下代码检查了这一点:

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 ++访问它。


请注意,在C和C ++中签名 int溢出是未定义的行为,因此您必须在没有实际引起它的情况下对其进行检测。有关添加之前的有符号的int溢出,请参阅在C / C ++中检测有符号的溢出


21
关于此主题的有用信息:Seacord的“ C和C ++中的安全编码”的第5章-http : //www.informit.com/content/images/0321335724/samplechapter/seacord_ch05.pdf C ++的SafeInt类-http ://blogs.msdn.com/david_leblanc/archive/2008/09/30/safeint-3-on-codeplex.aspx-http : //www.codeplex.com/SafeInt IntSafe C语言库:-[ blogs.msdn .com / michael_howard / archiv
Michael Burr

3
Seacord的安全编码是一个不错的资源,但是请不要使用IntegerLib。参见blog.regehr.org/archives/593
jww 2011年

32
gcc编译器选项-ftrapv将导致它在(有符号的)整数溢出上生成SIGABRT。看这里
nibot 2012年

1
它不能回答溢出问题,但是解决该问题的另一种方法是使用像GMP这样的BigNum库来确保您始终具有足够的精度。如果您预先分配了足够的数字,则不必担心溢出。
wrdieter 2013年

1
@HeadGeek在他的回答中提供的信息几乎也是我要说的。但是,加上一个。现在,您检测溢出的方式可能是最快的。正如我在HeadGeek的答案中所评论的那样,在ARM上,您可以使用clz指令或__clz(unsigned)函数来确定数字的等级(其最高位在哪里)。由于我不确定在x86或x64上是否可用,因此我将假定不是,并说找到最重要的位将采用最差的log(sizeof(int)*8)指令。
不知情的

Answers:


229

我看到您使用的是无符号整数。按照定义,在C语言中(我不了解C ++),无符号算术不会溢出...因此,至少对于C语言,您的观点很无聊:)

使用带符号的整数,一旦发生溢出,就会发生未定义的行为(UB),并且您的程序可以执行任何操作(例如:渲染测试不确定)。 

#include <limits.h>

int a = <something>;
int x = <something>;
a += x;              /* UB */
if (a < 0) {         /* Unreliable test */
  /* ... */
}

要创建一个合格程序,您需要产生所述溢出之前测试溢出。该方法也可以与无符号整数一起使用:

// For addition
#include <limits.h>

int a = <something>;
int x = <something>;
if ((x > 0) && (a > INT_MAX - x)) /* `a + x` would overflow */;
if ((x < 0) && (a < INT_MIN - x)) /* `a + x` would underflow */;

// For subtraction
#include <limits.h>
int a = <something>;
int x = <something>;
if ((x < 0) && (a > INT_MAX + x)) /* `a - x` would overflow */;
if ((x > 0) && (a < INT_MIN + x)) /* `a - x` would underflow */;

// For multiplication
#include <limits.h>

int a = <something>;
int x = <something>;
// There may be a need to check for -1 for two's complement machines.
// If one number is -1 and another is INT_MIN, multiplying them we get abs(INT_MIN) which is 1 higher than INT_MAX
if ((a == -1) && (x == INT_MIN)) /* `a * x` can overflow */
if ((x == -1) && (a == INT_MIN)) /* `a * x` (or `a / x`) can overflow */
// general case
if (a > INT_MAX / x) /* `a * x` would overflow */;
if ((a < INT_MIN / x)) /* `a * x` would underflow */;

对于除法(INT_MIN-1特殊情况除外),不可能超出INT_MININT_MAX


97
无符号整数也不会在C ++中严格溢出(ISO / IEC 14882:2003 3.9.1.4)。我在问题中对“溢出”的使用是口语化的,意在包括无符号类型的明确定义的包装,因为我对表示数学正整数而不是正整数mod 2 ^ 32(或2 ^ 64)。溢流是与数学上无穷大的整数行为的偏差,而溢流是语言中未定义的行为,这之间的区别似乎很难被清楚地表明。
克里斯·约翰逊

15
该测试不需要x >= 0- x > 0就足够了(如果x == 0,则x + a由于明显的原因而不会溢出)。
caf 2010年

2
@pmg,标准中是否有支持的报价?
Pacerier

5
我喜欢这种方法...但是,请注意:乘法溢出检测假定x为正。对于x == 0,它导致被零除的检测,对于x为负,它总是错误地检测到溢出。
Franz D.

4
if ((a < INT_MIN / x))测试为时已晚。甲if (x == -1) 测试首先需要。
chux-恢复莫妮卡

164

一种方法来确定操作是否可能溢出,使用最显著一个位的操作对象的位置和一点点基本的二进制数学知识。

另外,任何两个操作数的结果(最多)将比最大操作数的最高一位多1位。例如:

bool addition_is_safe(uint32_t a, uint32_t b) {
    size_t a_bits=highestOneBitPosition(a), b_bits=highestOneBitPosition(b);
    return (a_bits<32 && b_bits<32);
}

对于乘法,任何两个操作数都将(最多)产生这些操作数的位之和。例如:

bool multiplication_is_safe(uint32_t a, uint32_t b) {
    size_t a_bits=highestOneBitPosition(a), b_bits=highestOneBitPosition(b);
    return (a_bits+b_bits<=32);
}

同样,你可以估算的结果的最大尺寸a给力b是这样的:

bool exponentiation_is_safe(uint32_t a, uint32_t b) {
    size_t a_bits=highestOneBitPosition(a);
    return (a_bits*b<=32);
}

(当然,用位数代替目标整数。)

我不确定确定数字中最高一位的最快方法,这是一种蛮力方法:

size_t highestOneBitPosition(uint32_t a) {
    size_t bits=0;
    while (a!=0) {
        ++bits;
        a>>=1;
    };
    return bits;
}

它不是完美的,但这会让您很好地知道在执行操作之前是否有两个数字可能溢出。由于highestOneBitPosition函数中存在循环,我不知道这是否比简单地按照建议的方式检查结果更快,但是它可能会(特别是如果您事先知道操作数中有多少位)。


98
当然,您也可以重命名highestOneBitPosition来登录:)
Oliver Hallam,2010年

37
是的,它与的操作相同log2,但是对于没有数学背景的人来说,不一定很明显。
极客头

48
该算法是否会低估安全答案?由于SupremeOneBitPosition(2 ^ 31)= 32,因此2 ^ 31 + 0将被视为不安全。(2 ^ 32-1)* 1将被视为不安全,因为32 + 1> 32.由于1 * 100,因此1 ^ 100将被视为不安全。 > 32.
clahey 2010年

19
根据您的multiplication_is_safe 0x8000 * 0x10000溢出(位位置为16 + 17 = 33,即> 32),尽管这不是因为0x8000 * 0x10000 = 0x80000000它显然仍适合无符号32位int。这只是该代码无法使用的示例中的一个。0x8000 * 0x10001,...
Michi

13
@GT_mh:你的意思是?正如我所说,这并不完美;这是一条经验法则,它将明确地说出什么时候安全的,但如果不进行完整的计算,就无法确定每个计算是否都可以。0x8000 * 0x10000即使事实证明还可以,按照这个定义,它也不是“安全的”。
极客头球

147

Clang 3.4+GCC 5+提供了经过检查的算术内置函数。它们为这个问题提供了非常快速的解决方案,特别是与位测试安全检查相比。

对于OP问题中的示例,它将像这样工作:

unsigned long b, c, c_test;
if (__builtin_umull_overflow(b, c, &c_test))
{
    // Returned non-zero: there has been an overflow
}
else
{
    // Return zero: there hasn't been an overflow
}

Clang文档没有指定c_test如果发生溢出是否包含溢出结果,但是GCC文档说确实包含。鉴于这两个都喜欢__builtin兼容,因此可以安全地假设这也是Clang的工作方式。

有一个__builtin对每个运算,可以溢出(加法,减法,乘法),用符号和无符号的变体,对于int尺寸,长尺寸,以及长长的大小。名称的语法为__builtin_[us](operation)(l?l?)_overflow

  • u对于未签名s签名的 ;
  • 操作中的一个addsubmul;
  • 没有l后缀表示操作数为ints;一种l手段long; 2 l的平均值long long

因此,对于一个选中的带符号长整数加法运算,它将为__builtin_saddl_overflow。完整列表可以在Clang文档页面上找到。

GCC 5+和锵3.8+还提供通用内建的工作,而无需指定值的类型:__builtin_add_overflow__builtin_sub_overflow__builtin_mul_overflow。这些也适用于小于的类型int

内建组件降低到最适合该平台的组件。在x86上,它们检查进位,溢出和符号标志。

Visual Studio的cl.exe没有直接等效项。对于无符号的加法和减法,包括<intrin.h>将允许您使用addcarry_uNNsubborrow_uNN(其中NN是位数,例如addcarry_u8subborrow_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是输入上的进位/借位标志,返回值是输出上的进位/借位。它似乎没有等效的有符号运算或乘法。

否则,适用于Windows的Clang现在可以投入生产了(对于Chrome来说已经足够了),因此也可以选择。


__builtin_sub_overflow在Clang 3.4中绝对不是。
理查德·库克

2
@RichardCook,花了一些时间,但是Clang从3.9版开始具有通用的内置函数。
zneak

@tambre,我认为没有。
zneak

4
根据docs__builtin_add_overflow和朋友应该已经在Clang 3.8上可用。
Lekensteyn

2
谢谢。这很好。知道Visual C ++的对应功能是什么吗?似乎找不到它们。
Mudit Jain

53

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

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

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

11
...或使用numeric_limits <TYPE> :: max()
乔纳斯·古勒

20
别忘了处理a = 0-然后除法中断。
Thelema

16
@Thelema:“别忘了处理a = 0”-和INT_MIN / -1。
2011年

1
如果b == ULONG_MAX / a呢?鉴于没有残差的a划分,它仍然可以拟合ULONG_MAX

有趣的是,在性能方面,与除法相比乘法要快得多,并且您要为每个乘法添加一个除法。这听起来不像解决方案。
DrumM

40

警告:使用进行编译时,GCC可以优化掉溢出检查-O2-Wall在某些情况下,该选项会给您警告

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

但在此示例中不是:

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

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

编译 -fwrapv可解决问题,但会禁用某些优化。

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


8
请注意,编译器只能使用有符号整数类型执行此操作;溢出是完全为无符号整数类型定义的。不过,是的,这是一个相当危险的陷阱!
SamB 2012年

1
“我认为在进行不依赖溢出的优化时,编译器默认应发出警告。” -所以for(int k = 0; k < 5; k++) {...}应该提出警告?
user253751 '16

2
@immibis:为什么要这样?的值k可以在编译时轻松确定。编译器不必做任何假设。
MikeMB

2
@immibis:引用以上内容:“我认为在进行不依赖溢出的优化时,编译器默认应发出警告。”
MikeMB

1
@MikeMB优化n,在发出仅使用?的低5位的移位指令之前,编译器无需费心检查是否小于32 n
user253751's

30

Clang现在支持对有符号和无符号整数进行动态溢出检查。请参阅-fsanitize = integer开关。目前,它是唯一具有完全支持的动态溢出检查(用于调试)的C ++编译器。


25

我看到很多人回答了有关溢出的问题,但是我想解决他最初的问题。他说,问题是要找到一个b = c使得所有数字都无需重复就可以使用。好的,这不是他在这篇文章中所要求的,但是我仍然认为有必要研究问题的上限并得出结论,他永远不需要计算或检测溢出(请注意:我不是精通在数学上,所以我逐步进行了此操作,但是最终结果非常简单,可能会有一个简单的公式。

要点是问题对a,b或c要求的上限是98.765.432。无论如何,首先将问题分解为琐碎的部分和非琐碎的部分:

  • x 0 == 1(9、8、7、6、5、4、3、2的所有排列都是解)
  • x 1 == x(无解)
  • 0 b == 0(无解)
  • 1 b == 1(无解)
  • a b,a> 1,b> 1(非平凡)

现在我们只需要证明没有其他解决方案是可行的,只有排列有效(然后打印它们的代码是微不足道的)。我们回到上限。实际上上限是c≤98.765.432。这是上限,因为它是8位数字最大的数字(a和b分别为10位数字减去1)。此上限仅适用于c,因为a和b的界限必须低得多,因为我们可以计算出,因为b的指数变化是从2到上限:

    9938.08^2 == 98765432
    462.241^3 == 98765432
    99.6899^4 == 98765432
    39.7119^5 == 98765432
    21.4998^6 == 98765432
    13.8703^7 == 98765432
    9.98448^8 == 98765432
    7.73196^9 == 98765432
    6.30174^10 == 98765432
    5.33068^11 == 98765432
    4.63679^12 == 98765432
    4.12069^13 == 98765432
    3.72429^14 == 98765432
    3.41172^15 == 98765432
    3.15982^16 == 98765432
    2.95305^17 == 98765432
    2.78064^18 == 98765432
    2.63493^19 == 98765432
    2.51033^20 == 98765432
    2.40268^21 == 98765432
    2.30883^22 == 98765432
    2.22634^23 == 98765432
    2.15332^24 == 98765432
    2.08826^25 == 98765432
    2.02995^26 == 98765432
    1.97741^27 == 98765432

注意,例如最后一行:它说1.97 ^ 27〜98M。因此,例如1 ^ 27 == 1和2 ^ 27 == 134.217.728,这不是解决方案,因为它有9位数字(2> 1.97,因此实际上比应测试的数字大)。可以看出,可用于测试a和b的组合确实很小。对于b == 14,我们需要尝试2和3。对于b == 3,我们从2开始并在462处停止。所有结果都被允许小于〜98M。

现在,只需测试以上所有组合,然后查找不重复任何数字的组合即可:

    ['0', '2', '4', '5', '6', '7', '8'] 84^2 = 7056
    ['1', '2', '3', '4', '5', '8', '9'] 59^2 = 3481
    ['0', '1', '2', '3', '4', '5', '8', '9'] 59^2 = 3481 (+leading zero)
    ['1', '2', '3', '5', '8'] 8^3 = 512
    ['0', '1', '2', '3', '5', '8'] 8^3 = 512 (+leading zero)
    ['1', '2', '4', '6'] 4^2 = 16
    ['0', '1', '2', '4', '6'] 4^2 = 16 (+leading zero)
    ['1', '2', '4', '6'] 2^4 = 16
    ['0', '1', '2', '4', '6'] 2^4 = 16 (+leading zero)
    ['1', '2', '8', '9'] 9^2 = 81
    ['0', '1', '2', '8', '9'] 9^2 = 81 (+leading zero)
    ['1', '3', '4', '8'] 3^4 = 81
    ['0', '1', '3', '4', '8'] 3^4 = 81 (+leading zero)
    ['2', '3', '6', '7', '9'] 3^6 = 729
    ['0', '2', '3', '6', '7', '9'] 3^6 = 729 (+leading zero)
    ['2', '3', '8'] 2^3 = 8
    ['0', '2', '3', '8'] 2^3 = 8 (+leading zero)
    ['2', '3', '9'] 3^2 = 9
    ['0', '2', '3', '9'] 3^2 = 9 (+leading zero)
    ['2', '4', '6', '8'] 8^2 = 64
    ['0', '2', '4', '6', '8'] 8^2 = 64 (+leading zero)
    ['2', '4', '7', '9'] 7^2 = 49
    ['0', '2', '4', '7', '9'] 7^2 = 49 (+leading zero)

它们都不能解决问题(也可以通过缺少“ 0”,“ 1”,...,“ 9”来看出)。

解决该问题的示例代码如下。还要注意,这是用Python编写的,不是因为它需要任意精度的整数(代码不会计算大于9800万的整数),而是因为我们发现测试量很小,因此我们应该使用高级语言来完成。利用其内置的容器和库(另请注意:该代码有28行)。

    import math

    m = 98765432
    l = []
    for i in xrange(2, 98765432):
        inv = 1.0/i
        r = m**inv
        if (r < 2.0): break
        top = int(math.floor(r))
        assert(top <= m)

        for j in xrange(2, top+1):
            s = str(i) + str(j) + str(j**i)
            l.append((sorted(s), i, j, j**i))
            assert(j**i <= m)

    l.sort()
    for s, i, j, ji in l:
        assert(ji <= m)
        ss = sorted(set(s))
        if s == ss:
            print '%s %d^%d = %d' % (s, i, j, ji)

        # Try with non significant zero somewhere
        s = ['0'] + s
        ss = sorted(set(s))
        if s == ss:
            print '%s %d^%d = %d (+leading zero)' % (s, i, j, ji)

1
为什么不使用9.876.543.210作为上限?
汤姆·罗杰罗

3
因为等式的左侧必须使用2位数字。
hdante

2
不,它的确与众不同,但其上限实际上可以看作98765410为您所声明的LHS的值是> 1
保罗·柴尔兹

24

这是该问题的“非便携式”解决方案。Intel x86和x64 CPU具有所谓的EFLAGS寄存器,该寄存器在每次整数算术运算后由处理器填充。我将在此处跳过详细描述。相关标志是“ Overflow”标志(掩码0x800)和“ Carry”标志(掩码0x1)。为了正确解释它们,应该考虑操作数是有符号类型还是无符号类型。

这是从C / C ++检查标志的实用方法。以下代码将在Visual Studio 2005或更高版本(32位和64位)以及GNU C / C ++ 64位上运行。

#include <cstddef>
#if defined( _MSC_VER )
#include <intrin.h>
#endif

inline size_t query_intel_x86_eflags(const size_t query_bit_mask)
{
    #if defined( _MSC_VER )

        return __readeflags() & query_bit_mask;

    #elif defined( __GNUC__ )
        // This code will work only on 64-bit GNU-C machines.
        // Tested and does NOT work with Intel C++ 10.1!
        size_t eflags;
        __asm__ __volatile__(
            "pushfq \n\t"
            "pop %%rax\n\t"
            "movq %%rax, %0\n\t"
            :"=r"(eflags)
            :
            :"%rax"
            );
        return eflags & query_bit_mask;

    #else

        #pragma message("No inline assembly will work with this compiler!")
            return 0;
    #endif
}

int main(int argc, char **argv)
{
    int x = 1000000000;
    int y = 20000;
    int z = x * y;
    int f = query_intel_x86_eflags(0x801);
    printf("%X\n", f);
}

如果将操作数相乘而没有溢出,则将从中获得返回值0 query_intel_eflags(0x801),即未设置进位或溢出标志。在所提供的main()示例代码中,发生溢出,并且两个标志都设置为1。此检查并不意味着进行任何进一步的计算,因此它应该非常快。


21

如果您的数据类型大于要测试的数据类型(例如,您进行了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变得0xFFFFFFFF0x80变得0x80000000与最终uint16_t成为一个uint64_t

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


链接已断开:HTTP 403:禁止
Peter Mortensen

18

最简单的方法是将unsigned longs转换为unsigned long longs,进行乘法运算,然后将结果与0x100000000LL进行比较。

您可能会发现,这比示例中的分割效率更高。

哦,它可以同时在C和C ++中使用(因为您已经用这个标记了问题)。


刚刚看过glibc手册。提到整数溢出陷阱(FPE_INTOVF_TRAPSIGFPE。除了手册中令人讨厌的内容,这将是理想的:

FPE_INTOVF_TRAP 整数溢出(除非您以特定于硬件的方式启用溢出陷阱,否则在C程序中是不可能的)。

真的有点可耻。


4
嘿...我没有说的是,我在问这个问题是为了准备编写程序来解决较大数量的问题,其中我已经在使用long long int了。由于long long int不是(据说)在C ++标准中,因此我坚持使用32位版本以避免混淆。
克里斯·约翰逊,

我建议使用ULONG_MAX哪种方法比硬编码更容易键入和更易于移植0x100000000
jw013 2013年

24
这不工作时,longlong long具有相同的尺寸(例如,在许多64位编译器)。
interjay

无论如何,依靠信号告诉您有关溢出的情况确实很慢。
SamB 2014年

@SamB仅在预计溢出频繁发生的情况下。
user253751

17

这是一种检测至少溢出的溢出的快速方法,这可能会导致乘法,除法和幂运算。

这样做的原因是,正是因为处理器会将值回绕为零,并且C / C ++是从任何特定处理器中抽象出来的,所以您可以:

uint32_t x, y;
uint32_t value = x + y;
bool overflow = value < (x | y);

两者都确保了,如果一个操作数为零而一个操作数不是零,那么就不会错误地检测到溢出,并且比以前提到的许多NOT / XOR / AND / test操作要快得多。

如前所述,尽管这种方法比其他更为详尽的方法要好,但仍然是可优化的。以下是对包含优化的原始代码的修订:

uint32_t x, y;
uint32_t value = x + y;
const bool overflow = value < x; // Alternatively "value < y" should also work

一种检测乘法溢出的更有效,更便宜的方法是:

uint32_t x, y;
const bool overflow = (x >> 16U) * (y >> 16U);
uint32_t value = overflow ? UINT32_MAX : x * y;

这将导致UINT32_MAX溢出或相乘的结果。在这种情况下,完全不允许使用有符号整数进行乘法运算。


由于计算理论,我不同意..考虑以下情况:y> x,值溢出,由于设置了符号位(例如,对于无符号字符为1 + 255),y仅比x大测试值,并且x将导致在溢流= FALSE -因此使用的逻辑或防止这种破裂行为..
DX-MON

该测试适用于您给出的数字(x:= 1,y:= 255,size = uint8_t):值将为0(1 + 255)并且0 <1为真。它确实适用于每个数字对。
冈瑟·皮埃兹

嗯,你说的很对。我仍然坚持使用or技巧来保证安全,尽管任何好的编译器都会优化提供程序,但您确实对所有输入都是正确的,包括非溢出数字,例如“ 0 + 4”,结果不会溢出。
DX-MON 2012年

4
如果有溢出,则x+y>=256和和value=x+y-256。因为y<256始终成立,所以(y-256)为负,因此value < x始终成立。非溢出情况的证明非常相似。
冈瑟·皮埃兹

2
@ DX-MON:如果您还具有先前添加的进位位,则您的第一种方法是必需的。uint32_t x[N], y[N], z[N], carry=0; for (int i = 0; i < N; i++) { z[i] = x[i] + y[i] + carry; carry = z[i] < (x[i] | y[i]); }如果没有or这些值,则将无法区分一个操作数和进位为零,一个操作数为0xffffffff进位,而进位为一。
马特

14

您不能从C / C ++访问溢出标志。

一些编译器允许您将陷阱指令插入代码。在GCC上,选项为-ftrapv

您可以做的唯一可移植且独立于编译器的操作是自行检查溢出。就像您在示例中所做的一样。

但是,-ftrapv使用最新的GCC在x86上似乎无能为力。我想这是旧版本或其他一些体系结构遗留下来的。我曾希望编译器在每次添加后都插入一个INTO操作码。不幸的是,它没有这样做。


可能有所不同:-ftrapv在Cygwin盒上使用GCC 4.3.4似乎可以正常工作。在stackoverflow.com/questions/5005379
Nate Kohl

3
你们俩都是对的。-ftrapv做这项工作,但仅适用于有符号整数
ZAB

14

对于无符号整数,只需检查结果是否小于参数之一:

unsigned int r, a, b;
r = a + b;
if (r < a)
{
    // Overflow
}

对于带符号整数,您可以检查参数和结果的符号。

不同符号的整数不会溢出,并且相同符号的整数只有在结果具有不同符号的情况下才会溢出:

signed int r, a, b, s;
r = a + b;
s = a>=0;
if (s == (b>=0) && s != (r>=0))
{
    // Overflow
}

那么第一种方法也适用于有符号整数,不是吗?char result = (char)127 + (char)3;将是-126; 小于两个操作数。
primfaktor

1
哦,我知道了,问题在于它对于带符号的类型是未定义的。
primfaktor 2012年

27
-1的有符号数字溢出会导致不确定的行为(因此测试为时已晚,无法实际使用)。
Voo

1
@primfaktor对于带符号的int无效:char((-127)+(-17))=112。对于带符号的int,您必须检查参数和结果的符号位
phuclv 2014年

3
如前所述,有符号整数的解决方案不起作用,因为在发生溢出的情况下a + b的行为不确定。必须在操作之前检查带符号整数的溢出。
Marwan Burelle

11

对于浮点数,我需要回答相同的问题,在这种情况下,位掩码和移位看起来不太理想。我确定的方法适用于有符号和无符号,整数和浮点数。即使没有更大的数据类型可以推广到中间计算,它也可以工作。对于所有这些类型,它并不是最有效的,但是由于它确实对所有这些类型都有效,因此值得使用。

签名溢出测试,加法和减法:

  1. 获取常量,该常量表示类型MAXVALUE和MINVALUE的最大和最小值。

  2. 计算并比较操作数的符号。

    一个。如果任一值为零,则加法或减法都不会溢出。跳过其余测试。

    b。如果符号相反,则添加不会溢出。跳过其余测试。

    C。如果符号相同,则减法不会溢出。跳过其余测试。

  3. 测试MAXVALUE的正溢出。

    一个。如果两个符号均为正且MAXVALUE-A <B,则加法运算将溢出。

    b。如果B的符号为负且MAXVALUE-A <-B,则减法将溢出。

  4. 测试MINVALUE的负溢出。

    一个。如果两个符号均为负且MINVALUE-A> B,则加法运算将溢出。

    b。如果A的符号为负且MINVALUE-A> B,则减法将溢出。

  5. 否则,不会溢出。

签名溢出测试,乘法和除法:

  1. 获取常量,该常量表示类型MAXVALUE和MINVALUE的最大和最小值。

  2. 计算并比较操作数的大小(绝对值)为一。(下面,假设A和B是这些幅度,而不是签名的正本。)

    一个。如果任一值为零,则乘法不会溢出,除法将得出零或无穷大。

    b。如果任一值为1,则乘法和除法不会溢出。

    C。如果一个操作数的大小小于一个,而另一个大于一个,则乘法不会溢出。

    d。如果幅度均小于1,则除法运算不会溢出。

  3. 测试MAXVALUE的正溢出。

    一个。如果两个操作数均大于1且MAXVALUE / A <B,则乘法将溢出。

    b。如果B小于1并且MAXVALUE * B <A,则除法将溢出。

  4. 否则,不会溢出。

注意:MINVALUE的最小溢出由3处理,因为我们采用了绝对值。但是,如果ABS(MINVALUE)> MAXVALUE,那么我们将有一些罕见的误报。

下溢测试相似,但涉及EPSILON(最小的正数大于零)。


1
至少在POSIX系统上,可以为浮点下溢/上溢启用SIGFPE信号。
克里斯·约翰逊

在转换为浮点数和逆向工作时,(根据我在32位计算机上的测试),它比其他解决方案要慢得多。
JanKanis 2014年

审阅者检测到缺少相减部分2的案例。我同意0-MINVALUE将溢出。因此,应为此情况添加测试。
Paul Chernoch 2015年

<pedantic>整数不会下溢(=太接近零而无法准确表示)。1.0e-200 / 1.0e200假设IEEE翻倍,这将是实际下溢的示例。相反,这里的正确术语是负溢出。</ pedantic>
Arne Vogel,

确切地说,不将整数视为下溢的原因是由于已定义的截断行为,例如1/INT_MAX很可能被视为下溢,但是该语言仅将截断强制为零。
Arne Vogel,

8

CERT开发了一种新的方法,该方法使用“假设”无限范围(AIR)整数模型来检测和报告有符号整数溢出,无符号整数环绕和整数截断。CERT已发布技术报告描述该模型,并基于GCC 4.4.0和GCC 4.5.0制作了一个可运行的原型。

AIR整数模型产生的值等于使用无限范围整数获得的值,或者导致违反运行时约束。与以前的整数模型不同,AIR整数不需要精确的陷阱,因此不会破坏或抑制大多数现有的优化。


我看不到任何有用的链接,但这听起来像是我长期以来倡导的模型。它支持绝大多数有用的优化,同时还支持大多数实现可以免费提供的有用语义保证。如果代码知道在所有输出都很重要的情况下函数的输入都是有效的,但是事先不知道输出是否重要,那么在不影响任何内容的情况下能够让溢出发生可能是比必须不惜一切代价阻止它们更容易和更有效。
超级猫

8

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

这是一个补丁编译器,可在编译时向代码添加检查。

您将获得如下所示的输出:

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

1
现在,此修补程序已合并到其他消毒剂中的clang代码库中,请参阅我的答案。
ZAB 2013年

7

使用汇编语言的解决方案的另一个变体是外部过程。此示例在Linux x64下使用g ++和fasm进行无符号整数乘法。

此过程将两个无符号整数参数(32位)相乘(根据amd64的规范(第3.2.3节“ 参数传递”)。

如果该类是INTEGER,则使用序列%rdi,%rsi,%rdx,%rcx,%r8和%r9的下一个可用寄存器。

(edi和esi在我的代码中注册))并返回结果;如果发生溢出,则返回0。

format ELF64

section '.text' executable

public u_mul

u_mul:
  MOV eax, edi
  mul esi
  jnc u_mul_ret
  xor eax, eax
u_mul_ret:
ret

测试:

extern "C" unsigned int u_mul(const unsigned int a, const unsigned int b);

int main() {
    printf("%u\n", u_mul(4000000000,2)); // 0
    printf("%u\n", u_mul(UINT_MAX/2,2)); // OK
    return 0;
}

将程序与asm目标文件链接。就我而言,在Qt Creator中,将其添加到LIBS.pro文件中。


5

计算结果加倍。他们有15位有效数字。您的要求的c的上限是10 8-  最多可以有8位数字。因此,如果结果在范围内,则结果将是精确的,否则它将不会溢出。


5

尝试使用此宏来测试32位计算机的溢出位(适应Angel Sinigersky的解决方案)

#define overflowflag(isOverflow){   \
size_t eflags;                      \
asm ("pushfl ;"                     \
     "pop %%eax"                    \
    : "=a" (eflags));               \
isOverflow = (eflags >> 11) & 1;}

我将其定义为宏,因为否则溢出位将被覆盖。

随后是一个带有上面代码段的小应用程序:

#include <cstddef>
#include <stdio.h>
#include <iostream>
#include <conio.h>
#if defined( _MSC_VER )
#include <intrin.h>
#include <oskit/x86>
#endif

using namespace std;

#define detectOverflow(isOverflow){     \
size_t eflags;                      \
asm ("pushfl ;"                     \
    "pop %%eax"                     \
    : "=a" (eflags));               \
isOverflow = (eflags >> 11) & 1;}

int main(int argc, char **argv) {

    bool endTest = false;
    bool isOverflow;

    do {
        cout << "Enter two intergers" << endl;
        int x = 0;
        int y = 0;
        cin.clear();
        cin >> x >> y;
        int z = x * y;
        detectOverflow(isOverflow)
        printf("\nThe result is: %d", z);
        if (!isOverflow) {
            std::cout << ": no overflow occured\n" << std::endl;
        } else {
            std::cout << ": overflow occured\n" << std::endl;
        }

        z = x * x * y;
        detectOverflow(isOverflow)
        printf("\nThe result is: %d", z);
        if (!isOverflow) {
            std::cout << ": no overflow ocurred\n" << std::endl;
        } else {
            std::cout << ": overflow occured\n" << std::endl;
        }

        cout << "Do you want to stop? (Enter \"y\" or \"Y)" << endl;

        char c = 0;

        do {
            c = getchar();
        } while ((c == '\n') && (c != EOF));

        if (c == 'y' || c == 'Y') {
            endTest = true;
        }

        do {
            c = getchar();
        } while ((c != '\n') && (c != EOF));

    } while (!endTest);
}

4
并非所有的32位计算机都与Intel x86兼容,也不是所有的编译器都支持gnu汇编语法(我觉得很可笑的是,_MSC_VER尽管MS编译都会拒绝该代码,但您发布进行测试的代码)。
Ben Voigt,2015年

2

用C捕获整数溢出指出了一种解决方案,它比CERT讨论的解决方案更通用(就处理类型而言更通用),即使它需要一些GCC扩展(我不知道它们的支持程度如何)。


2

您不能从C / C ++访问溢出标志。

我不同意这一点。您可以编写一些内联汇编语言并使用jo(跳转溢出)指令(假设您使用的是x86)来捕获溢出。当然,您的代码将不再可移植到其他体系结构。

看看info asinfo gcc


8
内联汇编程序没有C / C ++功能,并且与平台无关。在x86上,您可以使用in指令代替分支btw。
Nils Pipenbrinck

0

为了扩展Head Geek的答案,有一种更快的方法addition_is_safe

bool addition_is_safe(unsigned int a, unsigned int b)
{
    unsigned int L_Mask = std::numeric_limits<unsigned int>::max();
    L_Mask >>= 1;
    L_Mask = ~L_Mask;

    a &= L_Mask;
    b &= L_Mask;

    return ( a == 0 || b == 0 );
}

这使用机器架构安全,因为64位和32位无符号整数仍然可以正常工作。基本上,我创建了一个遮罩,该遮罩将掩盖除最高有效位以外的所有部分。然后,我将两个整数都屏蔽了,如果其中两个都没有设置该位,那么加法是安全的。

如果在某些构造函数中预先初始化了掩码,这将更快,因为它永远不会改变。


5
这是不正确的。进位可能从较低位置带来一些位,这将导致溢出。考虑添加UINT_MAX + 1。屏蔽后,a将设置高位,但1将变为零,因此该函数将返回true,加法是安全的-但是您将直接进入溢出状态。


-1

MSalter的答案是一个好主意。

如果需要整数计算(以确保精度),但可以使用浮点数,则可以执行以下操作:

uint64_t foo(uint64_t a, uint64_t b) {
    double dc;

    dc = pow(a, b);

    if (dc < UINT_MAX) {
       return (powu64(a, b));
    }
    else {
      // Overflow
    }
}

通常,我会说在浮点中重复计算是一个坏主意,但是对于这种幂a ^ c的特殊情况,它可能会更有效。但是测试应该是(c * log(a) < max_log)在哪里const double max_log = log(UINT_MAX)
Toby Speight

-1

x86指令集包括一个无符号乘法指令,该指令将结果存储到两个寄存器中。要使用来自C的指令,可以在64位程序(GCC)中编写以下代码:

unsigned long checked_imul(unsigned long a, unsigned long b) {
  unsigned __int128 res = (unsigned __int128)a * b;
  if ((unsigned long)(res >> 64))
    printf("overflow in integer multiply");
  return (unsigned long)res;
}

对于32位程序,需要使结果为64位,参数为32位。

一种替代方法是使用依赖于编译器的内在函数来检查标志寄存器。可以从6.56内置函数通过溢出检查执行算术运算中找到有关溢出固有函数的GCC文档。


1
您应该使用无符号的128位类型,__uint128以避免有符号的溢出和右移负值。
chqrlie

什么是“依赖编译器的本能”“溢出本能”?您是说内在函数吗?你有参考吗?(请回复您的答案,而不是在评论中(适当时在此处)。)
Peter Mortensen

-3
#include <stdio.h>
#include <stdlib.h>

#define MAX 100 

int mltovf(int a, int b)
{
    if (a && b) return abs(a) > MAX/abs(b);
    else return 0;
}

main()
{
    int a, b;

    for (a = 0; a <= MAX; a++)
        for (b = 0; b < MAX; b++) {

        if (mltovf(a, b) != (a*b > MAX)) 
            printf("Bad calculation: a: %d b: %d\n", a, b);

    }
}

-3

一种干净的方法是覆盖所有运算符(尤其是+和*),并在执行操作之前检查是否有溢出。


6
除非您不能覆盖内置类型的运算符。您需要为此编写一个类并重写客户端代码以使用它。
Blaisorblade

-3

这取决于您使用它的目的。执行无符号长(DWORD)加法或乘法,最好的解决方案是使用ULARGE_INTEGER。

ULARGE_INTEGER是两个DWORD的结构。完整值可以作为“ QuadPart”来访问,而高DWORD可以作为“ HighPart”来访问,而低DWORD可以作为“ LowPart”来访问。

例如:

DWORD
My Addition(DWORD Value_A, DWORD Value_B)
{
    ULARGE_INTEGER a, b;

    b.LowPart = Value_A;  // A 32 bit value(up to 32 bit)
    b.HighPart = 0;
    a.LowPart = Value_B;  // A 32 bit value(up to 32 bit)
    a.HighPart = 0;

    a.QuadPart += b.QuadPart;

    // If  a.HighPart
    // Then a.HighPart contains the overflow (carry)

    return (a.LowPart + a.HighPart)

    // Any overflow is stored in a.HighPart (up to 32 bits)

6
不幸的是,这是仅Windows的解决方案。其他平台则没有ULARGE_INTEGER
2013年

-3

要执行无符号乘法而不会以可移植的方式溢出,可以使用以下方法:

... /* begin multiplication */
unsigned multiplicand, multiplier, product, productHalf;
int zeroesMultiplicand, zeroesMultiplier;
zeroesMultiplicand = number_of_leading_zeroes( multiplicand );
zeroesMultiplier   = number_of_leading_zeroes( multiplier );
if( zeroesMultiplicand + zeroesMultiplier <= 30 ) goto overflow;
productHalf = multiplicand * ( c >> 1 );
if( (int)productHalf < 0 ) goto overflow;
product = productHalf * 2;
if( multiplier & 1 ){
   product += multiplicand;
   if( product < multiplicand ) goto overflow;
}
..../* continue code here where "product" is the correct product */
....
overflow: /* put overflow handling code here */

int number_of_leading_zeroes( unsigned value ){
   int ctZeroes;
   if( value == 0 ) return 32;
   ctZeroes = 1;
   if( ( value >> 16 ) == 0 ){ ctZeroes += 16; value = value << 16; }
   if( ( value >> 24 ) == 0 ){ ctZeroes +=  8; value = value <<  8; }
   if( ( value >> 28 ) == 0 ){ ctZeroes +=  4; value = value <<  4; }
   if( ( value >> 30 ) == 0 ){ ctZeroes +=  2; value = value <<  2; }
   ctZeroes -= x >> 31;
   return ctZeroes;
}

-4

测试溢出的简单方法是通过检查当前值是否小于先前值来进行验证。例如,假设您有一个循环来打印2的幂:

long lng;
int n;
for (n = 0; n < 34; ++n)
{
   lng = pow (2, n);
   printf ("%li\n", lng);
}

添加溢出检查我描述的方式会导致以下结果:

long signed lng, lng_prev = 0;
int n;
for (n = 0; n < 34; ++n)
{
    lng = pow (2, n);
    if (lng <= lng_prev)
    {
        printf ("Overflow: %i\n", n);
        /* Do whatever you do in the event of overflow.  */
    }
    printf ("%li\n", lng);
    lng_prev = lng;
}

它适用于无符号值以及正负符号​​值。

当然,如果您想对减小值而不是增大值执行类似的操作,则可以使<=符号翻转以使其变为负值>=,并假定下溢的行为与上溢的行为相同。坦白地说,这几乎就像您在不访问CPU溢出标志的情况下所具有的可移植性一样(这将需要内联汇编代码,无论如何,这些代码都无法在实现之间移植)。


9
如果有符号值溢出,则程序的行为是不确定的。不保证可以环绕。
大卫·斯通
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.