为什么定义C#规范(int.MinValue / -1)实现?

表达式int.Minvalue / -1导致根据C#规范实现定义的行为:

7.8.2分部运营商

如果左操作数是最小的可表示的int或long值而右操作数是-1,则发生溢出。 在已检查的上下文中,这会导致抛出System.ArithmeticException(或其子类)。 在未经检查的上下文中,它是实现定义的 ,是否抛出System.ArithmeticException(或其子类)或溢出未报告,结果值是左操作数的值。

测试程序:

 var x = int.MinValue; var y = -1; Console.WriteLine(unchecked(x / y)); 

这会在.NET 4.5 32位上引发OverflowException ,但它不必。

为什么规范会保留结果实现定义? 这是反对这样做的情况:

  1. 在这种情况下,x86 idiv指令总是会导致exception。
  2. 在其他平台上,可能需要运行时检查来模拟它。 但是与该部门的成本相比,该检查的成本会很低。 整数除法非常昂贵(15-30个周期)。
  3. 这会打开兼容性风险(“一次写入无处可写”)。
  4. 开发者惊喜。

同样有趣的是,如果x / y是一个编译时常量,我们确实得到了unchecked(int.MinValue / -1) == int.MinValue

 Console.WriteLine(unchecked(int.MinValue / -1)); //-2147483648 

这意味着x / y可以根据所使用的句法forms具有不同的行为(并且不仅取决于xy的值)。 这是规范允许的,但它似乎是一个不明智的选择。 为什么C#这样设计?

类似的问题指出了规范中的哪个确切行为是规定的,但它没有(充分)回答为什么语言是这样设计的。 不讨论替代选择。

这是C#语言规范的更大兄弟Ecma-335 (公共语言基础结构规范)的副作用。 第III节,第3.31章描述了DIV操作码的作用。 C#规范经常必须遵循的规范,非常不可避免。 它指定它可以抛出但不要求它。

否则,对真实处理器的作用进行实际评估。 每个人都使用的是奇怪的。 英特尔处理器对溢出行为过于古怪,它们是在20世纪70年代设计的,假设每个人都会使用INTO指令。 没有人,另一天的故事。 它不会忽略IDIV上的溢出然后引发#DE陷阱,不能忽略那个响亮的爆炸声。

在不一致的处理器行为之上,在一个粗略的运行时规范之上编写语言规范非常困难。 很少有C#团队可以做到这一点,但转发不精确的语言。 他们已经通过记录OverflowException而不是ArithmeticException来超越规范。 很调皮。 他们看了一眼。

偷看了这种做法。 这不太可能是一个问题,抖动决定是否内联。 并且非内联版本抛出,期望内联版本也可以。 没有人失望过。

C#的主要设计目标据称是“最小惊喜法则”。 根据该指南,编译器不应该试图猜测程序员的意图,而应该向程序员发出信号,要求正确指定意图需要额外的指导。 这适用于感兴趣的情况,因为在二进制补码算法的限制内,操作产生了非常令人惊讶的结果: Int32.MinValue / -1计算为Int32.MinValue 。 发生了溢出,并且需要一个不可用的第33位(0)来正确表示Int32.MaxValue + 1的正确值。

正如预期的那样,并在您的引用中注明,在已检查的上下文中,会引发exception以提醒程序员未能正确指定意图。 在未经检查的上下文中,允许实现在检查的上下文中行为,或允许溢出并返回令人惊讶的结果。 有一些上下文,比如bit-twiddling,使用signed int是很方便的,但实际上是期望和期望溢出行为。 通过检查实现说明,程序员可以确定此行为是否实际上符合预期