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

usr*_*usr 24 c# language-design

该表达式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));
Run Code Online (Sandbox Code Playgroud)

这会引发OverflowException.NET 4.5 32位,但它没有.

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

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

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

Console.WriteLine(unchecked(int.MinValue / -1)); //-2147483648
Run Code Online (Sandbox Code Playgroud)

这意味着,x / y可以有根据使用的语法形式在不同的行为(而不是只依赖于的值xy).这是规范允许的,但它似乎是一个不明智的选择.为什么C#这样设计?

一个类似的问题指出,在说明书这一确切行为被规定,但它并没有(足够的)回答为什么语言是这样设计的.不讨论替代选择.

Han*_*ant 10

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

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

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

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


Pie*_*ens 7

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

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

  • 拥有未经检查的除法可能会很方便,但在Microsoft .NET上实际上并不存在.那是因为当操作数不变时,只发生溢出*.如果您需要未经检查的溢出,则无法获得它. (2认同)
  • @AndreasNiedermair运行第一个代码片段,它未经检查并抛出.(第二个未经检查,不会使用相同的输入.) (2认同)