【问题标题】:Why would you want an integer overflow to occur?为什么要发生整数溢出?
【发布时间】:2011-06-20 06:44:20
【问题描述】:

在这个问题中,主题是如何让 VS 检查 C# 中的算术溢出并抛出异常:C# Overflow not Working? How to enable Overflow Checking?

其中一位 cmets 说了一些奇怪的事情并得到了很多人的支持,我希望你能在这里帮助我:

您还可以使用 checked 关键字来包装一个语句或一组语句,以便显式检查它们是否存在算术溢出。设置项目范围的属性有点冒险,因为溢出通常是一个相当合理的预期。

我对硬件了解不多,但我知道溢出与寄存器的工作方式有关。我一直认为溢出会导致未定义的行为,应尽可能防止。 (在“正常”项目中,不编写恶意代码)

为什么您会期望发生溢出?如果有可能,为什么不总是阻止它? (通过设置相应的编译选项)

【问题讨论】:

  • 您的陈述“溢出导致未定义的行为”是不正确的......它的定义非常明确,尤其是在整数的情况下。在这种情况下,您的陈述“我对硬件知之甚少”说明了...您应该阅读一些有关二进制以及加法在机器级别的工作原理的内容
  • 这句话可能来自 C/C++ 的思维方式,溢出是一种“未定义的行为”,这意味着编译器编写者在优化时可以做你不期望的事情。 CPU 确实具有明确定义的行为并不重要,编译器编写器使用 bignum 常量表达式评估优化器,可能会检测到它并消除代码“它不是 C/C++ 程序,所以我可以破坏它”。我想,在 C# 中没有人需要关心,因为随着新硬件的不同,MS 已经转向了一些新的灵丹妙药
  • 在 C 和 C++ 中,对于无符号整数,溢出是非常明确的行为,但对于有符号整数,确实是未定义的行为。

标签: c# .net overflow integer-overflow


【解决方案1】:

想要溢出的主要时间是计算哈希码。在那里,结果的实际数值大小根本不重要——它实际上只是我碰巧用算术运算操作的位模式。

我们已经检查了 Noda Time 在项目范围内打开的算术 - 我宁愿抛出异常也不愿返回不正确的数据。我怀疑溢出是非常罕见的......我承认我通常将默认值保留为未经检查的算术,只是因为它是默认值。当然还有速度惩罚...

【讨论】:

  • 你的精度和速度真的很烦人。恭喜!
【解决方案2】:

我一直认为溢出的原因 未定义的行为,应该是 尽可能避免。

您可能还对缓冲区溢出(溢出)和数字溢出之间的区别感到困惑。

缓冲区溢出是指数据写入超过非托管数组的末尾。它可能导致未定义的行为,例如用用户输入的数据覆盖堆栈上的返回地址。在托管代码中很难做到缓冲区溢出。

然而,数值溢出是明确定义的。例如,如果您有一个 8 位寄存器,它只能存储 2^8 个值(如果无符号,则为 0 到 255)。所以如果你加上 100+200,你不会得到 300,而是 300 模 256,即 44。使用有符号类型的情况稍微复杂一些;位模式以类似的方式递增,但它们被解释为two's complement,因此添加两个正数可以得到一个负数。

【讨论】:

  • +1 以获得很好的解释。我知道缓冲区和算术溢出之间的区别,但仍然认为它(至少在理论上)在所有情况下都是未定义的行为。
  • 你知道溢出是由谁定义的吗?是硬件吗?是平台(.NET、Java 等)吗?假设您想从 32 位机器上的 .NET 程序和 64 位机器上的 Java 程序编写整数哈希码(构造忽略溢出)。假设所有整数的大小相同,那么哈希码是否有意义?
  • @Marco - 实际上,在大多数情况下,整数溢出在每个级别都会得到很好的定义。例如,C#:“任何超出结果类型范围的重要高位都将被丢弃”,Java:“如果整数加法溢出,则结果是数学和的低位为以某种足够大的二进制补码格式表示”。
  • @Marco - 如果您知道计算步骤都相同,那么来自 C# 和 Java 的哈希代码将具有可比性。如果您在这两种情况下编写实际代码来计算它们,我只会依赖它。如果您使用相同大小和符号类型的整数,则在 Java 和 C# 中将两个整数相加会得到相同的结果。
【解决方案3】:

在使用不断递增的计数器进行计算时。一个经典的例子是 Environment.TickCount:

int start = Environment.TickCount;
DoSomething();
int end = Environment.TickCount;
int executionTime = end - start;

如果选中该选项,则该程序有可能在 Windows 启动 27 天后被炸毁。当 DoSomething 运行时 TickCount 超过 int.MaxValue 时。 PerformanceCounter 是另一个例子。

即使存在溢出,这些类型的计算也会产生准确的结果。第二个例子是您为生成具有代表性的位模式所做的那种数学运算,您对准确的结果并不真正感兴趣,只是对可重复的结果感兴趣。例如校验和、哈希和随机数。

【讨论】:

  • 在这种情况下,异常很可能比下溢要好——通常中止比产生错误的数据要好,这些数据可能会通过系统级联。当然,视情况而定。
  • 除非溢出不止一次。 :)
  • @darron - 这与 one 毫秒的时间测量有关。 “多次溢出”角度为 27 天。或 54,视情况而定。
【解决方案4】:

角度

溢出的整数是测量角度的优雅工具。你有 0 == 0 度,0xFFFFFFFF == 359.999.... 度。它非常方便,因为作为 32 位整数,您可以添加/减去角度(350 度加 20 度最终溢出环绕回 10 度)。您还可以决定将 32 位整数视为有符号(-180 到 180 度)和无符号(0 到 360)。 0xFFFFFFF 相当于 -179.999...,相当于 359.999...,这是等价的。很优雅。

【讨论】:

    【解决方案5】:

    当生成 HashCodes 时,比如说从一串字符。

    【讨论】:

      【解决方案6】:

      如果有可能,为什么不总是阻止它?

      默认情况下未启用检查算法的原因是检查算法比未检查算法慢。如果性能对您来说不是问题,那么启用检查算法可能是有意义的,因为发生溢出通常是一个错误。

      【讨论】:

      • 好吧,我不知道,这听起来很合理。那仍然无法解释为什么评论指出“通常溢出是一个相当合理的期望”?看看它被投票的频率(2 小时内 +10),他似乎不是唯一一个有这种观点的人?
      • 区别主要在于有符号算术。由于 2 的补数的工作方式,8 位 11111111 实际上是负 1 而不是 sByte.MinValue -128(如您所料)。不过,这是有道理的;将 1 添加到 -1 应该导致零,因此将 00000001 添加到 11111111 == 00000000。这在技术上是算术溢出;字节左端的进位位 1 在分配的字节中没有任何可去处,因此丢失了。然而,整数总是在正负之间变化,所以这应该只是你想要的错误。
      【解决方案7】:

      这可能与历史有关,也可能与任何技术原因有关。依赖于行为的算法(尤其是散列算法)经常使用整数溢出来产生良好的效果。

      此外,大多数 CPU 都设计为允许溢出,但在处理过程中设置了一个进位位,这使得在比自然字长更长的字长上实现加法更容易。在这种情况下实现检查操作意味着如果设置了进位标志,则添加代码以引发异常。不是一个巨大的强加,但编译器作者可能不想强加给没有选择的人。

      替代方法是默认检查,但提供未选中的选项。为什么这不是那么可能也可以追溯到历史。

      【讨论】:

        【解决方案8】:

        您可能会期望它在测量增量的东西上。一些网络设备使计数器大小保持较小,您可以轮询一个值,例如传输的字节数。如果值变得太大,它只会溢出回零。如果您经常测量它(字节/分钟、字节/小时),它仍然会为您提供一些有用的信息,并且由于计数器通常在连接断开时被清除,所以它们并不完全准确并不重要。

        正如贾斯汀所说,缓冲区溢出是另一回事。这是您不应该将数组末尾写入内存的地方。在数值溢出中,使用相同数量的内存。在缓冲区溢出中,您使用未分配的内存。在某些语言中会自动防止缓冲区溢出。

        【讨论】:

          【解决方案9】:

          有一个关于程序员在程序设计中利用溢出的经典故事:

          The Story of Mel

          【讨论】:

          • 这是一个有用的答案吗?
          • 虽然是一个有趣的故事(可能是程序员的都市传奇),但除非您在固件或超低级别工作,否则这些天没有人应该做这样的事情。绝对不是在 C# 中
          • 应该是评论,如果有的话。
          【解决方案10】:

          这与寄存器的工作方式没有太大关系,因为它只是存储数据的变量中的内存限制。 (您可以溢出内存中的变量而不会溢出任何寄存器。)

          但要回答您的问题,请考虑最简单的校验和类型。它只是被检查的所有数据的总和。如果校验和溢出,没关系,没有溢出的部分还是有意义的。

          其他原因可能包括您只是希望程序继续运行,即使无关紧要的变量可能已经溢出。

          【讨论】:

          • CPU 不在内存上做任何工作,它们在寄存器上工作。一旦完成工作,寄存器就会被复制到内存中。所以这真的是关于寄存器如何溢出。
          • 您究竟是从哪里获得有关 CPU 从未直接与内存一起工作的信息的? ADD MyVar,1 甚至 ADD [esi],1 这样的语句中的寄存器溢出在哪里? CPU 绝对可以直接在内存上工作。
          【解决方案11】:

          我可以想象的另一种可能的情况是随机数生成算法 - 在这种情况下我们不考虑溢出,因为我们想要的只是一个随机数。

          【讨论】:

            【解决方案12】:

            整数溢出是这样的。

            你有一个 8 位整数 1111 1111,现在给它加 1。 0000 0000,前导的 1 被截断,因为它将在第 9 位。

            现在假设你有一个有符号整数,前导位表示它是负数。所以现在你有 0111 1111。给它加 1 你有 1000 0000,即 -128。在这种情况下,将 1 加到 127 使其变为负数。

            我很确定溢出的行为方式很明确,但我不确定下溢。

            【讨论】:

              【解决方案13】:

              所有整数算术(至少加上减法和乘法)都是精确的。这只是您需要注意的结果位的解释。在 2 的补码系统中,您会得到正确的结果,以 2 为模数对位数进行取模。有符号和无符号之间的唯一区别是,对于有符号数,最高有效位被视为符号位。由程序员决定什么是合适的。显然,对于某些计算,您想了解溢出并在检测到溢出时采取适当的措施。就我个人而言,我从来不需要溢出检测。我使用了一个依赖它的线性同余随机数生成器,即 64*64bit 无符号整数乘法,我只关心最低的 64 位,由于截断,我免费获得模运算。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2017-09-04
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-02-06
                • 2014-09-19
                相关资源
                最近更新 更多