算术溢出检查如何在c#中工作

时间:2014-09-04 15:57:47

标签: c#

由于性能原因,似乎默认情况下c#中的算术溢出检查已关闭(请参阅https://stackoverflow.com/a/108776/2406234等)。

但是如果我打开它,使用/ checked标志或checked关键字,运行时实际上如何执行检查? (我试图更好地理解这个'性能如何发挥作用。)

2 个答案:

答案 0 :(得分:8)

算术运算,在硬件级别一直向下提供支持,指示执行的操作是否导致溢出。在许多情况下,这些信息将被忽略(并且通常会在非常低的级别忽略),但是可以在每次操作后检查此结果,如果发生溢出,则抛出一个新的异常。所有这些检查,以及通过各种抽象层传播这些信息,当然都有成本。

答案 1 :(得分:6)

C#中的溢出检查通过在CIL中使用(或不使用)溢出检查来工作。

例如,考虑C#代码:

public static int AddInts(int x, int y)
{
    return x + y;
}

如果没有溢出检查,它将被编译为类似:

.method public hidebysig static int32 AddInts(int32 x, int32 y) cil managed 
{
    .maxstack 2
    IL_0000: ldarg.0
    IL_0001: ldarg.1
    IL_0002: add
    IL_0003: ret
}

使用溢出检查,它将被编译为类似:

.method public hidebysig static int32 AddInts(int32 x, int32 y) cil managed 
{
    .maxstack 2
    IL_0000: ldarg.0
    IL_0001: ldarg.1
    IL_0002: add.ovf
    IL_0003: ret
}

如您所见,CIL表单使用add的不同溢出检查和非溢出检查形式,这同样适用于checkedunchecked的每个操作影响C#中的行为。这在混合了大量已检查和未检查操作的代码中会更方便,但绝大多数时候,大多数时候一个组合在一起检查或在方法中一起检查,因此C#的默认方法是在一个块中重写的程序集几乎一直对人类编码器来说更方便。

这个CIL在进行jitted时会发生什么事情取决于它所处理的处理器。可能的结果是要么使用类似的溢出检查指令(这会导致溢出中断,这可能用于产生.NET想要的异常)或者像jo这样的跳转溢出指令86。

  

我试图更好地了解这个'表现如何?'就是这样。

unchecked确实几乎总是和checked一样快,并且通常更快,因为忽略溢出可以比阻止溢出更频繁地允许更有效的路径。简单地说,检查溢出有时需要另外一个动作,尽管在大多数时候处于低水平的非常快,并且做某事几乎总是比什么都不做要快。*因此,如果你知道溢出可以'发生这种情况时,如果不检查那种不可能的情况,它确实有一点性能上的好处。

但是,这应被视为unchecked的次要功能。

主要特征是它改变算术运算的意思

对于两个32位整数(例如),unchecked(x + y)表示"添加x和y并将结果强制转换为32位二进制补码数"而checked(x + y)表示"添加x和y并返回32位二进制补码数,结果为"。

因此,当unchecked(int.MaxValue + int.MaxValue)返回-2时,是正确答案,而checked(int.MaxValue + int.MaxValue) 没有正确答案因此引发了例外。

它们确实是不同的操作,并且在很多情况下我们想要 -2第一次返回。

因此,我们主要担心的是,如果我们超越类型的限制,那么正确的答案是什么?"

  1. 丢弃比特导致的答案是正确答案,因为我们将这些值视为比特集:使用unchecked
  2. 我们将这些值视为表示某些计数或度量的整数,但它们的高度意味着某种错误,或者我们尚未准备好处理的数量:使用checked
  3. 我们将这些值视为表示某些计数或度量的整数,并且对于此类值仍应正确处理数学:使用longBigInteger而不是int你可以正确处理所有可能性。 (如果性能分析显示它有很大帮助,可能只有int的快速路径。)
  4. 我们正在考虑将这些值作为整数,但我们要么相信它们永远不会如此规模,要么可以忍受具有奇怪结果的那些因为它必须是&#的情况34;垃圾垃圾焚烧":严格来说这适合checked使用,但unchecked将具有相同的结果,因此我们可以使用它来获得轻微的性能提升。
  5. 人们处理每种情况的频率取决于他们的节目是什么。可能大多数程序通常会处理第四种情况(你的程序多少次处理几百万次?),我们应该在语义上使用checked的情况,但它没有真正的区别,所以我们可能同时获得unchecked性能提升。

    第一种情况在某类案件中非常普遍,特别是相对较低级别的案件;程序算法通常适用于其大多数业务逻辑中的第四种情况。这是图书馆正在处理的许多低级东西中的第一个。

    当我们需要一些接近"真实世界"数学和int无法削减它,那么我们通常仍然需要"真实的"结果,所以我们在第三种情况的某些变体中。

    真的,第一个做算术的案例,偶尔会说"对不起,我无法处理这个"很少是一种理想的行为; OverflowException通常是一种异常,它告诉开发人员他们遇到问题而不是我们捕获的那种,然后变成对他们有帮助的用户的错误消息。因此,大多数情况下,当我们有第一个案例时,我们认为我们有第四个案例,但我们错了。

    因此,它可能有用:

    1. 将符合第一种情况的所有代码(绝对应该是unchecked)标记为unchecked,即使这对项目默认值是多余的。
    2. 标记所有真正应该引用OverflowException的代码,因为您将对checked执行有用的操作(很少见)。
    3. 大部分时间都使用unchecked来提升性能,但是始终使用checked进行调试,偶尔会运行单元测试,或者如果你有一些奇怪的行为。 (这里需要注意的平衡取决于应用程序)。
    4. *还存在分支错误预测的可能性,尽管最常见的情况是非溢出情况,并且最常见的情况也是如此。一般来说,分支误预测不像其他情况那样大,包括手动检查溢出。