为什么这个程序没有溢出?

时间:2017-06-24 19:19:02

标签: c# integer-overflow

试着研究C#如何对溢出作出反应,我写了这个简单的代码:

static uint diff(int a, int b)
{
    return (uint)(b - a);
}
static void Main(string[] args)
{
    Console.Out.WriteLine(int.MaxValue);
    uint l = diff(int.MinValue, int.MaxValue);
    Console.Out.WriteLine(l);
    Console.In.ReadLine();
}

我得到了这个输出:

2147483647
4294967295

我感到惊讶的是它运行得很好,因为diff中的int减法应该得到大于int.MaxValue的结果。

但是,如果我写这个,这似乎等同于上面的代码:

uint l = (uint)(int.MaxValue - int.MinValue);

C#甚至不会编译,因为代码可能会溢出。

是什么让第一个代码在没有溢出的情况下运行,而编译器甚至不会编译第二行?

1 个答案:

答案 0 :(得分:12)

使用常量值时:

uint l = (uint)(int.MaxValue - int.MinValue);

编译器确切知道你要做什么,因为它知道值,并且它看到减法的结果不适合int,所以它会给你错误。

使用变量时:

return (uint)(b - a);

编译器在编译时不知道变量的值是什么,所以它不会抱怨。

请注意,溢出位于int,而非uint,如您在现在删除的答案中所述。您可能认为从小值中减去了大值,但事实并非如此。 int.MinValue实际上是负数(-2147483648),减去它意味着你实际上正在添加它(2147483647 - (-2147483648)),所以结果(4294967295)不能适合int,但它可以适合uint。例如,这将编译并给出正确的结果4294967295:

uint x = (uint)((long)int.MaxValue - int.MinValue);

因为现在你告诉编译器将减法结果存储在long而不是int中,这样就可以了。现在将x打印到控制台,注意减法的结果是4294967295,而不是-1,因为你在答案中说明了。如果它像你说的那样是-1,那么下面的代码应该编译,但它不会因为4294967295溢出int

int x = int.MaxValue - int.MinValue;

编辑有更多人在努力理解结果,所以这里有更多解释,我希望能有所帮助:

首先,我们都知道int.MaxValue是2147483647,int.MinValue是-2147483648。我希望我们都同意这个简单的数学,而不必在程序中证明它:

2147483647 - (-2147483648) = 4294967295

所以我们都应该同意数学结果是4294967295,而不是-1。如果有人不同意,请回到学校。

那么为什么程序中的结果有时为-1会让很多人感到困惑呢?

好的,我们都同意发生溢出,所以这不是问题。有些人不明白溢出的发生在哪里。在转换为uint时不会发生这种情况。肯定-1会溢出uint,但程序在转换为int之前在步骤中溢出uint。发生溢出时,程序行为会根据执行上下文(已选中或未选中)而有所不同。在已检查的上下文中,抛出OverflowException,之后不会执行任何操作,因此不会执行转换为uint。在未经检查的上下文中,结果的最高有效位被丢弃并继续执行,因此执行转换为uint并发生另一次溢出。这是an MSDN article about how the integer overflows behave

那么让我们看看我们如何获得-1:

首先,在C#中,当你减去两个整数时,结果是一个整数。现在,如果结果不能适合整数,则会发生溢出。棘手的部分是在未经检查的上下文中,结果的最高有效位被丢弃,就像我上面提到的那样。在问题的场景中,结果为-1。以下是一些我希望能够明确说明的例子:

Console.WriteLine(unchecked(int.MaxValue)); //Result 2147483647
Console.WriteLine(unchecked(int.MinValue)); //Result -2147483648
Console.WriteLine(unchecked(int.MaxValue-int.MinValue)); //Result -1 overflow
Console.WriteLine(unchecked(2147483647-(-2147483648))); //Same as above
Console.WriteLine(unchecked(int.MaxValue+int.MinValue)); //Result -1 no overflow
Console.WriteLine(unchecked(2147483647+(-2147483648))); //Same as above
Console.WriteLine(unchecked(int.MaxValue+1)); //Result -2147483648 overflow
Console.WriteLine(unchecked(2147483647+1)); //Same as above
Console.WriteLine(unchecked(int.MaxValue-int.MaxValue)); //Result 0
Console.WriteLine(unchecked(2147483647-2147483647)); //Same as above
Console.WriteLine(unchecked(int.MaxValue+int.MaxValue)); //Result -2 overflow
Console.WriteLine(unchecked(2147483647+2147483647)); //Same as above

这些例子的结果应该清楚。我没有在这里做任何演员以避免关于溢出发生在哪里的争论,所以很明显它发生在int。每次发生溢出时,就好像第一个int被分配了int.MinValue的值-2147483648,然后第二个int被添加/减去。

如果您将第一个号码转换为long,则结果将为long。现在不会发生溢出,您将获得与数学相同的结果:

Console.WriteLine((long)int.MaxValue); //Result 2147483647
Console.WriteLine((long)int.MinValue); //Result -2147483648
Console.WriteLine((long)int.MaxValue-int.MinValue); //Result 4294967295
Console.WriteLine((long)2147483647-(-2147483648)); //Same as above
Console.WriteLine((long)int.MaxValue+int.MinValue); //Result -1
Console.WriteLine((long)2147483647+(-2147483648)); //Same as above
Console.WriteLine((long)int.MaxValue+1); //Result 2147483648
Console.WriteLine((long)2147483647+1); //Same as above
Console.WriteLine((long)int.MaxValue-int.MaxValue); //Result 0
Console.WriteLine((long)2147483647-2147483647); //Same as above
Console.WriteLine((long)int.MaxValue+int.MaxValue); //Result 4294967294
Console.WriteLine((long)2147483647+2147483647); //Same as above

以下是不使用任何加法/减法的证据。只需将值高于int.MaxValue,就会导致unchecked转换为int.MinValue的溢出。任何大于int.MaxValue + 1的值都会添加到int.MinValue

Console.WriteLine(unchecked((int)2147483647)); //Result 2147483647
Console.WriteLine(unchecked((int)2147483648)); //Result -2147483648 overflow
Console.WriteLine(unchecked((int)2147483649)); //Result -2147483647 overflow
Console.WriteLine(unchecked((int)2147483649)); //Result -2147483647 overflow
Console.WriteLine(unchecked((int)2147483650)); //Result -2147483646 overflow
Console.WriteLine(unchecked((int)2147483651)); //Result -2147483645 overflow

int溢出int.MinValue以下的值时,恰好相反:

Console.WriteLine(unchecked((int)-2147483648)); //Result -2147483648
Console.WriteLine(unchecked((int)-2147483649)); //Result 2147483647 overflow
Console.WriteLine(unchecked((int)-2147483650)); //Result 2147483646 overflow
Console.WriteLine(unchecked((int)-2147483651)); //Result 2147483645 overflow

这使得int像无限旋转的微调器一样工作。两端相互粘在一起,所以当你到达一端时,你翻到另一端并继续1 2 3 1 2 3 1 2 3。