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

Mar*_*ner 46 c# unchecked checked overflowexception

int y = -2147483648;
int z = unchecked(y / -1);
Run Code Online (Sandbox Code Playgroud)

第二行引起了OverflowException.不应该unchecked阻止这个?

例如:

int y = -2147483648;
int z = unchecked(y * 2);
Run Code Online (Sandbox Code Playgroud)

不会导致例外.

Ser*_*rvy 33

C#4规范的第7.72节(分部运营商)声明:

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

因此,在未经检查的上下文中抛出异常这一事实实际上并不是一个错误,因为该行为是实现定义的.

  • 有趣.在旧版本的.NET中,行为是[已定义](http://msdn.microsoft.com/en-us/library/aa691373(v = vs.71).aspx):*如果左操作数是最小的可表示的`int`或`long`值,右操作数是`-1`,发生溢出.无论操作是在`checked`还是`unchecked`上下文中,都会在这种情况下抛出`System.OverflowException`.* (12认同)
  • @Neolisk但是,这就是重点.规范定义了您可以从C#程序中获得的行为.当使用这些操作数调用时,特定处理器指令碰巧发生错误只意味着如果你知道这个C#代码导致那个精确的处理器指令被称为该C#代码的完整实现(你不知道).有人可以在非英特尔机器上运行相同的C#代码并让它返回`int.MinValue`.在编写这个C#代码时,你确实需要假设它可能有任何一种行为,你甚至不能假设它会抛出. (6认同)
  • @Neolisk C#规范没有说明当你打这个电话时,任何事情都可能发生,包括宇宙爆炸.它特别声明它返回第一个操作数或抛出.你*可以*依赖于这两件事之一,你只是不知道哪一件.如果我正在编写这段代码,我不知道知道哪些CPU会有哪些行为可能会有用.无论如何,你必须编写代码以支持这两种情况.这不像你应该编写代码来支持一个然后说,"你只能在intel机器上运行这个程序". (4认同)
  • 也许您也可以引用Hans Passant的答案,该答案提供了实施细节,即究竟为什么它不适用于OP?说"因为行为未定义,我们看到一些奇怪的东西"本身并不是一个答案. (3认同)
  • @Neolisk显然,OP已经知道他的特定CPU碰巧选择了哪两个选项,因为他说代码正在为他投掷.说他正在使用的CPU恰好抛出这种情况而不是返回第一个操作数似乎毫无意义.无论哪种方式,当使用这个C#代码时,他需要编写代码,好像行为是未定义的,而不是依赖于任何一种行为,无论他使用什么CPU. (2认同)
  • @CodesInChaos很明显它是抛出的指令.如果C#代码在check/throw中添加,那么行为将不会被定义,毫无疑问会抛出.至于为什么C#团队选择使用这个实现,我不会试图猜测为什么他们决定这样做并把它放在答案中.表现可能是原因,也可能不是,无论如何我都无法评论它; 只有C#团队的人才会. (2认同)

Han*_*ant 32

这不是C#编译器或抖动可以控制的例外.它特定于Intel/AMD处理器,当IDIV指令失败时,CPU会生成#DE陷阱(Divide Error).操作系统处理处理器陷阱并使用STATUS_INTEGER_OVERFLOW异常将其反射回进程.CLR尽职尽责地将其转换为匹配的托管异常.

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

非整数结果被截断(切断)为0.余数总是小于除数.溢出用#DE(除错误)异常表示,而不是CF标志.

在英语中:签名除法的结果是+2147483648,在int中不可表示,因为它是Int32.MaxValue + 1.否则,处理器表示负值的方式不可避免的副作用,它使用二进制补码编码.它产生一个代表0的值,留下奇数个其他可能的编码来表示负值和正值.负值还有一个.-Int32.MinValue除了处理器没有捕获NEG指令并且只产生垃圾结果之外,它的溢出程度相同.

C#语言当然不是唯一遇到此问题的语言.C#语言规范通过注意特殊行为使其成为实现定义的行为(第7.8.2节).没有其他合理的事情可以用它做,生成处理异常的代码肯定被认为太不实用,产生不可忽视的慢代码.不是C#方式.

C和C++语言通过使其成为未定义的行为来规范赌注.这可能真的变得丑陋,就像使用gcc或g ++编译器编译的程序一样,通常使用MinGW工具链.其中对SEH的运行时支持不完善,它吞下异常并允许处理器重新启动除法指令.该程序挂起,在处理器不断生成#DE陷阱的情况下烧掉100%核心.将分裂转变为传奇的Halt和Catch Fire指令 :)

  • 但是C#语言不应该生成会导致这样的错误的代码(或者它应该吞下异常并处理情况而不是重新抛出),以便履行`unchecked'的合同. .当处理器抛出错误时C#重新抛出错误的事实并没有真正解释任何事情. (9认同)
  • Hmya,这就是你用软件炸毁火箭的方式. (3认同)
  • 对于你的编辑,`unchecked`的重点是*not*在整数操作溢出时抛出溢出异常,而是换行. (2认同)

Mar*_*far 10

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

7.8.2除法运算符
对于x/y形式的运算,应用二元运算符重载决策(第7.3.4节)来选择特定的运算符实现.操作数将转换为所选运算符的参数类型,结果的类型是运算符的返回类型.下面列出了预定义的除法运算符.运算符都计算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则抛出a.该部门将结果舍入为零.因此,结果的绝对值是小于或等于两个操作数的商的绝对值的最大可能整数.当两个操作数具有相同符号时,结果为零或正,当两个操作数具有相反符号时,结果为零或负.如果左操作数是最小的可表示的int或long值而右操作数是-1,则发生溢出.在检查的上下文中,这会导致System.ArithmeticException抛出(或其子类).在未经检查的上下文中,它是实现定义的,是否System.ArithmeticException抛出(或其子类)或溢出未报告,结果值是左操作数的值.

  • @Neolisk C#的不同版本.他引用了5.0,我引用了4.0.显然相关内容没有任何不同. (6认同)
  • 为什么你最后引用了规格的不同部分,但用相同的词语? (2认同)