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

30

经Carl Norum提醒,已编辑以包含适当的标准参考。

C标准规定:

如果在表达式求值期间发生了“异常情况”(即结果在其类型的可表示值范围之外或在数学上未定义),则行为是未定义的。

是否有编译器开关可以保证整数溢出时的某些行为?我想避免出现未知的错误。特别地,我想强制编译器在溢出时进行包装。

为了保持唯一性,让我们将标准定为C99,编译器为gcc。但我也对其他编译器(icc、cl)和其他标准(C1x、C89)的答案感兴趣。事实上,为了激怒C/C ++群体,我甚至希望得到C++0x、C++03和C++98的答案。

注意:国际标准ISO / IEC 10967-1可能与此相关,但据我所知,它仅在说明性附件中提到。

7个回答

26

请看一下-ftrapv-fwrapv

-ftrapv

这个选项会对加、减、乘法操作的有符号溢出生成陷阱。

-fwrapv

这个选项告诉编译器假设有符号算术加、减、乘法溢出会使用二进制补码表示法进行回绕。这个标志启用了一些优化并禁用了其他优化。Java前端要求默认启用此选项,以符合Java语言规范。


1
太好了,这正是我想要看到的。对于无符号类型,有类似的东西吗? - Charles
3
@Charles,对于无符号类型,你不需要它们——溢出行为已经被明确定义了(请参见我的答案)。 - Carl Norum
@Carl Norum:我看到它在C++中被定义,并且在C中用于移位(6.5.7第5段)。我看不到它在C标准中用于加法和乘法的定义。 - Charles
1
@Charles - 6.2.5第9段:“包含无符号操作数的计算永远不会溢出,因为不能用结果无法表示的无符号整数类型被减去一个比该类型能表示的最大值还要大1的数字。” - Carl Norum
13
请注意,-fwrapv选项并不保证溢出会被包装回来。它仅告诉编译器优化器可以假设溢出会被包装回来,而这是否为真取决于您的机器架构。 - caf
1
@caf:你知道-fwrapv和-fno-strict-overflow在可能影响包装行为的转换方面是否有区别吗?例如,将“int1 + int2 + long1”转换为“(int1 + long1) + long2”?我认为有一个选项可以启用精确包装,另一个选项可以明确接受与正确结果同余(模整数大小),因为每种行为在某些情况下可能是必需的。 - supercat

18
对于您的C99答案,我认为您需要的是第6.5节“表达式”,第5段:
如果在表达式求值时发生“异常条件”(即,如果其结果在数学上未定义或不在其类型的可表示值范围内),则行为是未定义的。
这意味着,如果发生溢出,您将没有任何保证的行为。无符号类型是一种特殊情况,永远不会溢出(第6.2.5节“类型”,第9段):
涉及无符号操作数的计算永远不会溢出,因为结果无法由生成的无符号整数类型表示的结果被减少模比生成类型可表示的最大值大1的数字。
C++具有相同的语句,措辞略有不同:
- 第5节“表达式”,第4段:
如果在表达式求值期间,结果在数学上未定义或不在其类型的可表示值范围内,则行为是未定义的。[注意:C++的大多数现有实现都忽略整数溢出。使用零除数形成余数以及所有浮点例外情况的处理在机器之间变化,并且通常可以通过库函数进行调整。—注释]
- 第3.9.1节“基本类型”,第4段:
声明为unsigned的无符号整数应遵守算术模2 ^ n的规律,其中n是该特定大小的整数的值表示中的位数。

是的,这正是我在寻找的内容。(尽管一开始参考文献让我有些困惑——那是6.5段落5,而不是6.5.5节。)你知道有什么方法可以避免这种情况吗?溢出时的包装很常见,而且有很多时候我希望发生这种情况。是否有一些流行的编译器有一个开关,让它们承诺进行包装呢? - Charles
@Charles,未定义行为是未定义的。你可能会在特定编译器上有好运-检查其文档中能让你放心的声明。例如,在gcc中,你可以查看-fstrict-overflow-fwrapv标志。 - Carl Norum

7

在C99中,一般行为描述在6.5/5中。

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

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

涉及无符号操作数的计算永远不会溢出,因为不能由所得无符号整数类型表示的结果将被减少模比所得类型可以表示的最大值大1的数字。

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


5

1
现在的GCC也有整数溢出内置函数。请参阅https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html。 - Viktor Söderqvist

2
之前的帖子都评论了C99标准,但事实上这种保证早在此前就已经存在了。C89标准的第6.1.2.5节类型的第5段声明:无符号操作数的计算永远不会溢出,因为一个不能用得到的无符号整数类型表示的结果将被模除比该无符号整数类型能表示的最大值大1的数字。请注意,这使得C程序员可以将所有无符号除法替换为某个常数的乘法,以代替使用C的模2^N区间算术所形成的环的逆元素。这样做不需要任何“校正”,因为近似除法与倒数值的固定点乘法是必要的。相反,可以使用扩展欧几里得算法来找到逆元素并将其用作乘数。(当然,为了保持可移植性,应该还应用位AND运算以确保结果具有相同的位宽。)值得一提的是,大多数C编译器已经将此作为优化实现了。然而,这样的优化并不是保证的,因此在速度很重要但C优化器的能力要么未知要么特别弱的情况下,程序员手动执行这样的优化仍然可能很有意义。最后需要说明的是,为什么要尝试这样做:机器级别的乘法指令通常比除法指令快得多,特别是在高性能CPU上。

谢谢。C89对我来说确实很有兴趣,因为我编写的一个项目就是用这种方言编写的。 - Charles

2

你需要查找的是6.2.5第9段:

有符号整数类型的非负值范围是相应无符号整数类型的子范围,并且每种类型中相同值的表示方式相同。31)涉及无符号操作数的计算永远不会溢出,因为不能由结果无符号整数类型表示的结果将被模除比结果类型可以表示的最大值大1的数字。


0

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


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