C:标准和编译器中的整数溢出

时间:2010-09-09 17:36:25

标签: c gcc standards undefined

感谢Carl Norum编辑包含适当的标准参考。

C标准声明

  

如果在评估表达式期间发生异常情况(即,如果结果未在数学上定义或未在其类型的可表示值范围内),则行为未定义。

是否有编译器开关可以保证整数溢出时的某些行为?我想避免鼻腔恶魔。特别是,我想强制编译器包装溢出。

为了唯一性,让标准为C99,编译器为gcc。但我会对其他编译器(icc,cl)和其他标准(C1x,C89)的答案感兴趣。事实上,只是为了惹恼C / C ++人群,我甚至会欣赏C ++ 0x,C ++ 03和C ++ 98的答案。

注意:国际标准ISO / IEC 10967-1可能与此相关,但据我所知,仅在资料性附录中提及。

7 个答案:

答案 0 :(得分:21)

看看-ftrapv and -fwrapv

  

-ftrapv

     

此选项为加法,减法,乘法运算的带符号溢出生成陷阱。

     

-fwrapv

     

该选项指示编译器假设加法,减法和乘法的有符号算术溢出使用二进制补码表示。此标志启用一些优化并禁用其他优化。默认情况下,Java前端根据Java语言规范的要求启用此选项。

答案 1 :(得分:13)

对于你的C99答案,我认为 6.5表达式,第5段是你正在寻找的:

  

如果在评估表达式期间发生异常情况(即,如果结果未在数学上定义或未在其类型的可表示值范围内),则行为未定义。

这意味着如果你遇到溢出,你就不走运了 - 没有任何保证的行为。无符号类型是一种特殊情况,永不溢出( 6.2.5类型,第9段):

  

涉及无符号操作数的计算永远不会溢出,因为无法用结果无符号整数类型表示的结果将以比结果类型可以表示的最大值大1的数量为模。

C ++有相同的陈述,措辞有点不同:

  • 5个表达,第4段:

      

    如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,则行为未定义。 [注意:大多数现有的C ++实现忽略整数溢出。除零处理,使用零除数形成余数,所有浮点异常因机器而异,通常可通过库函数调整。 -endnote

  • 3.9.1基本类型,第4段:

      

    无符号整数,声明为unsigned,应遵守算术模2 ^ n 的定律,其中 n 是该值的表示中的位数特定大小的整数。

答案 2 :(得分:7)

在C99中,一般行为以6.5 / 5

表示
  

如果发生异常情况   在评估表达时   (也就是说,如果结果不是   在数学上定义或不在   其代表值的范围   类型),行为未定义。

6.2.5 / 9中描述了无符号类型的行为,它基本上表明对无符号类型的操作永远不会导致异常情况

  

涉及无符号的计算   操作数永远不会溢出,因为a   无法表示的结果   得到的无符号整数类型是   减少模数是一个数   大于最大值   可以用结果来表示   类型。

GCC编译器有一个特殊选项-ftrapv,用于捕获有符号整数操作的运行时溢出。

答案 3 :(得分:4)

为了完整起见,我想补充一点,Clang现在已经“检查算术内置函数”作为语言扩展。以下是使用checked无符号乘法的示例:

unsigned x, y, result;
...
if (__builtin_umul_overflow(x, y, &result)) {
    /* overflow occured */
    ...
}
...

http://clang.llvm.org/docs/LanguageExtensions.html#checked-arithmetic-builtins

答案 4 :(得分:2)

6.2.5第9段是您正在寻找的:

  

有符号整数类型的非负值范围是。的子范围   相应的无符号整数类型,以及每个类型中相同值的表示   类型是相同的.31)涉及无符号操作数的计算永远不会溢出,   因为无法用结果无符号整数类型表示的结果是   减少模数可以是最大值的数字   由结果类型表示。

答案 5 :(得分:1)

以前的帖子都对C99标准进行了评论,但实际上,这种保证早就可以使用了。

第6.1.2.5节“类型”的第5段

C89标准状态

  

涉及无符号操作数的计算永远不会溢出,   因为结果不能用结果无符号表示   整数类型的模数小于1   产生的无符号整数类型可以表示的最大值。

请注意,这允许C程序员用某个常数替换所有无符号除法,并用与C的2 ^ N区间模算术形成的环的逆元素的乘法来替换。

这可以在不进行任何“校正”的情况下完成,因为通过对定值乘以与倒数相乘来近似除法,将是必要的。

相反,扩展欧几里得算法可用于查找逆元素并将其用作乘数。 (当然,为了保持可移植性,还应应用按位与运算,以确保结果具有相同的位宽。)

可能值得一提的是,大多数C编译器已经将其实现为优化。但是,不能保证这样的优化,因此对于程序员来说,在速度很重要的情况下手动执行这样的优化可能仍然很有趣,但是C优化器的功能要么未知,要么特别弱。

最后要说的是,为什么要这么做:乘法的机器级指令通常比除法的指令快得多,尤其是在高性能CPU上。

答案 6 :(得分:0)

我不确定是否有任何编译器开关可用于在C / C ++中强制执行溢出的统一行为。另一种选择是使用SafeInt<T>模板。它是一个跨平台的C ++模板,为所有类型的整数操作提供明确的上溢/下溢检查。