【问题标题】:Why dividing int.MinValue by -1 threw OverflowException in unchecked context?为什么将 int.MinValue 除以 -1 会在未经检查的上下文中抛出 OverflowException?
【发布时间】:2014-12-23 01:11:37
【问题描述】:
int y = -2147483648;
int z = unchecked(y / -1);

第二行导致OverflowExceptionunchecked 不应该阻止这种情况吗?

例如:

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

不会导致异常。

【问题讨论】:

  • @DJBurb 问题不是问如何解决它,而是解释行为。代码不应抛出异常。
  • 执行除法时唯一可以想到的溢出 int 的方法是使用此处列出的两个操作数,即将 int.MinValue 除以 -1。其他每一对操作数都不会溢出,所以他们可能只是没有想到这种情况并错误地假设整数除法永远不会溢出。
  • @HansPassant 我理解您删除了您的答案,尽管这很不幸。 (这里的系统可能有问题...)
  • @HansPassant - 他们不是真正的开发者!
  • @HansPassant:你的赞成票比反对票多,我认为你应该把你的答案带回来。特别是因为这是Servy答案的后半部分/另一半。

标签: c# checked unchecked overflowexception


【解决方案1】:

这不是 C# 编译器或抖动可以控制的异常。它特定于 Intel/AMD 处理器,当 IDIV 指令失败时,CPU 会生成 #DE 陷阱(除法错误)。操作系统处理处理器陷阱并将其反射回进程,并带有 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 的运行时支持不完善,它吞下异常并允许处理器重新启动除法指令。程序挂起,烧毁 100% 内核,处理器不断生成#DE 陷阱。化师为传奇Halt and Catch Fire instruction:)

【讨论】:

  • 但是 C# 语言不应该首先生成会导致这种错误的代码(或者它应该吞下异常并处理这种情况而不是重新抛出),以便履行unchecked的合同。 C# 在处理器抛出错误时重新抛出错误这一事实并不能真正解释任何事情。
  • 对于您的编辑,unchecked 的全部意义在于在整数运算溢出时抛出溢出异常,而是进行换行。
  • neg 在 -2147483648 上的结果不是垃圾,实际上是 +2147483648(当解释为无符号时)
  • 嗯,这就是你用软件炸毁火箭的方法。
【解决方案2】:

C# 4 规范的第 7.72 节(除法运算符)指出:

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

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

【讨论】:

  • 也许您还可以引用 Hans Passant 的回答,它提供了实现细节,即究竟为什么它不适用于 OP?说“因为行为未定义,我们看到了一些奇怪的东西”本身很难回答。
  • @Neolisk 但这就是重点。规范定义了您可以从 C# 程序中获得什么行为。仅当您知道此 C# 代码导致该确切处理器指令被称为该 C# 代码的完整实现(您不知道)时,使用这些操作数调用特定处理器指令时碰巧引发错误才有意义。有人可以在非英特尔机器上运行相同的 C# 代码并让它返回 int.MinValue。在编写此 C# 代码时,您确实需要假设它可能具有任何一种行为,您甚至不能假设它会抛出异常。
  • 有趣。在旧版本的 .NET 中,行为是 defined如果左操作数是可表示的最小 intlong 值,而右操作数是 –1,则会发生溢出。在这种情况下总是抛出System.OverflowException,无论操作是在checked 还是unchecked 上下文中发生。
  • @Neolisk 显然 OP 已经知道他的特定 CPU 碰巧选择了两个选项中的哪一个,因为他说代码正在为他抛出。说他使用的 CPU 恰好在这种情况下抛出而不是返回第一个操作数似乎毫无意义。无论哪种方式,在使用此 C# 代码时,他都需要编写代码,就好像行为未定义一样,而不是依赖于任何一种行为,而不管他使用的是什么 CPU。
  • @Neolisk C# 规范并没有说任何事情都可能发生,包括宇宙爆炸,当您进行此调用时。它特别声明它要么返回第一个操作数,要么抛出。您可以依靠发生的这两件事中的一件,只是不知道是哪一件。如果我正在编写这段代码,我看不出知道哪些 CPU 会有哪些行为会有什么用处。无论如何,您都必须编写代码来支持这两种情况。这不像你应该编写代码只支持一个,然后说,“你只能在英特尔机器上运行这个程序”。
【解决方案3】:

根据C# Language Specification 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。除法将结果四舍五入到零。因此,结果的绝对值是小于或等于两个操作数的商的绝对值的最大可能整数。当两个操作数具有相同符号时,结果为零或正,当两个操作数具有相反符号时,结果为零或负。 如果左操作数是可表示的最小 int 或 long 值,而右操作数是 –1,则会发生溢出。在检查的上下文中,这会导致抛出 System.ArithmeticException(或其子类)。 在未经检查的上下文中,是否抛出 System.ArithmeticException(或其子类)或溢出未报告,结果值为左操作数的值,由实现定义。

【讨论】:

  • 你为什么最后引用了规范的不同部分,但用了相同的词?
  • @Neolisk 不同版本的 C#。他引用5.0,我引用4.0。显然相关内容并没有什么不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-26
  • 2017-08-28
  • 1970-01-01
  • 1970-01-01
  • 2011-10-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多