为什么默认不检查溢出

Why isn't overflow checked by default

我在 SO 上发现了一些关于在执行 over/underflow 行为之前检查操作的问题。似乎有很多方法可以很容易地做到这一点。那么,为什么没有一个选项可以在执行之前自动检查每个数学运算,或者为什么没有 buffer over/underflow 算术运算的异常?或者换句话说:在什么情况下允许操作溢出而不被注意会有用?

可能是运行时间的问题?还是在非数学运算期间发生溢出的主要来源?

Java 语言只是没有将此功能内置为关键字或直接应用于 +-* 运算符的机制。例如,C# 为此具有 checkedunchecked 关键字。但是,当语言没有本机支持时,这些检查可能 成本高 并且难以实施。至于Java 1.8,评论中的方法addExact, subtractExact and multiplyExact have been added to the API to provide this feature, as pointed out by @Tom

为什么这不会自动完成,即使语言支持它?简单的答案是,一般来说,上溢和下溢可以被接受或想要的行为,或者它们根本不会发生,因为一个复杂且执行良好的设计它应该是。我会说,利用溢出和下溢是一个低级或硬件编程问题,以避免出于性能原因的额外操作。

总的来说,您的应用程序设计应该明确说明 合理 使用算术上溢和下溢,或者最好根本不需要使用它们,因为它会导致混淆,不直观的行为或严重的错误。在第一种情况下你不检查,在第二种情况下检查将毫无用处。自动检查是多余的,只是成本效益。

一个人为的想要溢出的例子,可以是一个计数器。假设您有一个未签名的空头并计算它。 65536之后因为溢出又归零了,可以方便点。

我可以提供两个潜在的因素来说明为什么未经检查的算法是默认值:

  • 熟悉感: C 和C++ 中的算术运算在默认情况下是不选中的,习惯了这些语言的人不会期望程序抛出,而是默默地继续。这是一个误解,因为 C 和 C++ 在有符号整数 overflow/underflow 上都有未定义的行为。但是尽管如此,它已经在许多人的脑海中产生了一定的期望,并且同一家族的新语言往往回避明显地打破既定惯例。
  • 基准性能: 检测 overflow/underflow 通常需要执行比您决定忽略它时更多的指令。想象一下,如果一个不熟悉一门新语言的人编写了一个数学密集型基准测试(这种情况经常发生)并且 "proved" 即使对于最简单的数学运算,该语言也比 C 和 C++ 慢得多,它会是什么样子。这会损害人们对该语言性能的看法,并可能阻碍其采用。

实际上,对于 C 有检查选项,请参见此处:http://danluu.com/integer-overflow/

至于 java,添加整数溢出检查会打开一大堆蠕虫。由于 java 不提供无符号类型,无符号数学通常以纯 int 或 long 类型完成 - 显然 VM 不会神奇地意识到预期操作的无符号性质,这意味着您需要添加无符号类型或程序员需要非常注意转动支票on/off。在 Arrays.binarySearch 中可以找到带有符号类型的无符号数学示例。附带一提,Java 确实定义了溢出情况下的结果,因此依赖溢出行为是对已定义行为的合法使用。

正如在上面的 C link 中简要分析的那样,这些检查在实践中可能会对性能产生严重影响,因为粗略的实施 and/or 会干扰其他代码优化。

此外,虽然大多数 CPU 可以检测到溢出(通常通过 C 和 V 标志),但它们同时对 signed/unsigned 进行检测(常见的 CPU ISA 不会在 add/sub 的情况下 signed/unsigned 操作之间的区别)。由程序来响应这些标志,这意味着在代码中插入额外的指令。同样,这意味着 programmer/compiler 必须知道该操作是要签名还是不签名才能做出正确的选择。

所以溢出检测确实是有代价的,尽管它可以通过良好的编译器支持变得相当小。

但在许多情况下,溢出要么是设计上不可能的(例如,函数的有效输入参数不能产生溢出),而是需要的(例如,环绕行为计数器),或者当它们确实发生时被其他方式捕获使用结果(例如,通过数组边界检查)。

我必须努力思考我真正感到需要进行溢出检查的情况。通常您更关心在特定点(例如函数参数)验证值范围。但是这些是对函数特定值范围的任意检查,编译器甚至不知道(好吧,在某些语言中它会知道,因为它明确表达,但 Java 和 C 都不属于这一类)。

所以溢出检查并不是普遍有用的。这并不意味着没有任何它可以防止的潜在错误,但与其他错误类型相比,溢出并不是一个真正常见的问题。我不记得上次看到整数溢出导致的错误是什么时候了。例如,差一个错误要普遍得多。另一方面,有一些显式依赖于溢出环绕的微优化(例如我的一个老问题,参见已接受的答案:Performance: float to int cast and clipping result to range)。

对于所描述的情况,强制 C/Java 检查和响应整数溢出会使它们成为 更糟糕的 语言。它们会更慢,and/or 程序员会简单地停用该功能,因为它比它更有用。这并不意味着溢出检查作为一种语言特性通常是不好的;但要真正从中得到一些东西,环境也需要适应(例如,如上所述,Java 需要无符号类型)。

TL;DR 它可能很有用,但它需要更深入的语言支持,而不仅仅是一个有用的开关。