【问题标题】:Why does the C# specification leave (int.MinValue / -1) implementation defined?为什么 C# 规范要定义 (int.MinValue / -1) 实现?
【发布时间】:2015-08-02 18:14:39
【问题描述】:

表达式 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 指令总是会导致异常。
  2. 在其他平台上,可能需要运行时检查来模拟这一点。但是,与部门的成本相比,该支票的成本会很低。整数除法非常昂贵(15-30 个周期)。
  3. 这会带来兼容性风险(“一次写入就无法运行”)。
  4. 开发者惊喜。

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

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

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

similar question 指出了规范中规定了这种确切行为的位置,但它没有(充分)回答为什么语言是这样设计的。没有讨论替代选择。

【问题讨论】:

  • dimitry 在this answer 上的评论表明规范并不总是这样读。
  • @HenkHolterman 这个问题是关于语言设计的。建议的副本没有回答这个问题。 (重新打开我自己的问题有点尴尬。)
  • why 部分在骗子的答案中得到了充分的回答。当您认为您有更具体的问题时,您应该链接到原始问题并说明差异。
  • @HenkHolterman 现在会这样做。
  • @HenkHolterman 是的,他解释了我所知道的 x86 情况。但是为什么要定义这个实现呢?我不反对抛出异常。我想知道为什么甚至允许替代行为。

标签: c# language-design


【解决方案1】:

这是 C# 语言规范的老大哥 Ecma-335 公共语言基础结构规范的副作用。第 III 节第 3.31 章描述了 DIV 操作码的作用。 C# 规范经常不得不遵循的规范,这是不可避免的。它指定它可能抛出但不要求它。

否则,对真实处理器的工作进行现实评估。每个人都使用的那个是奇怪的。英特尔处理器对溢出行为过于古怪,它们是在 1970 年代设计的,假设每个人都会使用 INTO 指令。没有人这样做,改天再讲一个故事。但是,它不会忽略 IDIV 上的溢出并引发#DE 陷阱,不能忽略那一声巨响。

在处理器行为不一致的情况下,在冗长的运行时规范之上编写语言规范非常困难。 C# 团队对此几乎无能为力,只能转发不精确的语言。他们已经通过记录 OverflowException 而不是 ArithmeticException 超出了规范。很调皮。他们偷看了一下。

一瞥揭示了这种做法。这不太可能是一个问题,抖动决定是否内联。 non-inlined version 抛出,期望内联版本也可以。还没有人失望过。

【讨论】:

  • ECMA 要求例外。这将为 C# 也强制它扫清道路。尽管div 永远不会溢出,但为什么他们允许不抛出的情况?
  • 它没有,它只是说它可能抛出一个 ArithmeticException。 CLI 规范中的强制行为的措辞非常不同。你可以随心所欲地阅读它,但你知道 C# 团队是如何阅读它的 :)
  • 它在“III.3.31”中说:“如果结果无法以结果类型表示,则积分运算会抛出 System.ArithmeticException。(如果 value1 是可表示的最小整数值,并且 value2是-1。)“。没有其他情况。这不是在文档中查看的正确位置吗?在这一点上,C# 规范对我来说似乎是一个错误或历史性事故。
  • 这是正确的地方。它确实没有说“这发生”。
【解决方案2】:

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

正如预期的那样,并在您的引用中指出,在检查的上下文中会引发异常以提醒程序员未能正确指定意图。在未检查的上下文中,允许实现与检查的上下文中的行为相同,或者允许 溢出 并返回令人惊讶的结果。在某些情况下,例如位旋转,使用带符号的 int 很方便,但溢出行为实际上是预期和期望的。通过查看实现说明,程序员可以确定这种行为是否真的如预期的那样

【讨论】:

  • 不加检查的除法可能很方便,但在 Microsoft .NET 上实际上并不存在。这是因为溢出 only 在操作数为常量时发生。如果您需要未经检查的溢出,则无法获得它。
  • @usr C# 编译器不是使用未选中的,而 VB.Net 默认使用选中的吗?另见stackoverflow.com/a/13259794/57508
  • @AndreasNiedermair 运行第一个代码 sn-p,它未经检查并抛出。 (第二个未选中,并且不会使用相同的输入抛出。)
猜你喜欢
  • 2014-12-07
  • 2018-07-31
  • 1970-01-01
  • 2016-11-08
  • 1970-01-01
  • 2018-01-27
  • 1970-01-01
  • 1970-01-01
  • 2018-06-08
相关资源
最近更新 更多