为什么将int.MinValue除以-1会在未经检查的上下文中抛出OverflowException?

int y = -2147483648; int z = unchecked(y / -1); 

第二行导致一个OverflowException 。 不应该阻止这个?

例如:

 int y = -2147483648; int z = unchecked(y * 2); 

不会导致exception。

这不是C#编译器或者抖动可以控制的例外。 它是特定于Intel / AMD处理器的,当IDIV指令失败时,CPU产生一个#DE陷阱(Divide Error)。 操作系统处理处理器陷阱并将其反映到进程中,出现STATUS_INTEGER_OVERFLOWexception。 CLR忠实地将其转换为匹配的托pipeexception。

“英特尔处理器手册”并不完全是有关它的信息的金矿:

非积分结果被截断(斩波)为0.余数总是小于除数的幅度。 溢出用#DE(除错)exception而不是CF标志表示。

在英文中:有符号除法的结果是+2147483648,因为它是Int32.MaxValue + 1,所以不能在int中表示。否则,处理器表示负值的方式的不可避免的副作用,它使用2的补码编码。 它产生一个单一的值来表示0,留下奇数的其他可能的编码来表示负值和正值。 还有一个负值。 与-Int32.MinValue相同的溢出-Int32.MinValue ,除了处理器不会在NEG指令上进行陷阱并产生垃圾结果。

C#语言当然不是唯一有这个问题的人。 C#语言规范通过注意特殊的行为来实现定义的行为(第7.8.2节)。 没有其他合理的事情,他们可以用它来处理exception的代码,被认为太不切实际,产生不可想象的慢代码。 不是C#的方式。

C和C ++语言通过使其不明确的行为来预测事件的发生。 这可以真正变丑,就像使用gcc或g ++编译器编译的程序,通常使用MinGW工具链。 对SEH的运行时支持不完善,吞并exception并允许处理器重新启动除法指令。 该程序挂起,与处理器不断产生#DE陷阱燃烧100%的核心。 把部门变成传说中的Halt and Catch Fire指令 🙂

C#4规范的7.72节(部门操作员)指出:

如果左操作数是最小的可表示的int或long值,右操作数是-1,则发生溢出。 在一个检查的上下文中,[…]。 在未经检查的上下文中, 实现定义是否抛出了System.ArithmeticException(或其子类)或溢出未被报告,结果值是左操作数的值。

所以这个在未经检查的上下文中抛出exception的事实并不是一个错误,因为这个行为是由实现定义的。

根据C#语言规范5.0的第7.8.2节,我们有以下情况:

7.8.2分部操作员
对于x / yforms的操作,应用二元运算符重载决策(第7.3.4节)来select特定的运算符实现。 操作数被转换为所选操作符的参数types,结果的types是操作符的返回types。 下面列出了预定义的划分运算符。 运算符都计算x和y的商。

  • 整数除法:
    int operator /(int x, int y);
    uint operator /(uint x, uint y);
    long operator /(long x, long y);
    ulong operator /(ulong x, ulong y);
    如果右操作数的值为零,则抛出System.DivideByZeroException 。 该部门将结果向零调整。 因此,结果的绝对值是小于或等于两个操作数的商的绝对值的最大可能整数。 当两个操作数具有相同的符号时,结果为零或正值,当两个操作数具有相反的符号时,结果为负值或负值。 如果左操作数是最小的可表示的int或long值,右操作数是-1 ,则发生溢出。 在检查的上下文中,这会导致System.ArithmeticException (或其子类)被抛出。 在未经检查的上下文中,实现定义是否抛出了System.ArithmeticException (或其子类)或溢出未被报告,结果值是左操作数的值。
Interesting Posts