25

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

其中一条评论说得很奇怪并得到了很多人的支持,我希望你能在这里帮助我:

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

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

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

4

13 回答 13

36

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

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

于 2011-02-02T21:24:03.620 回答
10

我一直认为溢出会导致未定义的行为,应尽可能防止。

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

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

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

于 2011-02-02T21:26:19.947 回答
9

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

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

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

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

于 2011-02-02T21:31:27.630 回答
5

角度

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

于 2011-02-03T03:03:00.500 回答
3

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

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

于 2011-02-02T21:21:17.790 回答
3

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

于 2011-02-02T21:24:27.817 回答
3

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

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

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

于 2011-02-02T21:27:15.070 回答
2

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

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

于 2011-02-02T21:30:54.607 回答
1

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

梅尔的故事

于 2011-02-02T21:22:39.493 回答
1

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

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

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

于 2011-02-02T21:26:01.723 回答
0

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

于 2011-02-02T21:25:59.273 回答
0

整数溢出是这样的。

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

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

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

于 2011-02-02T22:08:06.683 回答
0

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

于 2011-02-03T00:14:20.843 回答