为什么这个程序不会溢出?

Why doesn't this program 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.MaxValue 的结果。

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

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

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

为什么第一行代码 运行 没有溢出,而编译器甚至不会编译第二行?

当您使用常量值时:

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,但程序在转换为 uint 之前的步骤中溢出了 int。发生溢出时,程序行为会根据执行上下文(选中或未选中)而有所不同。在已检查的上下文中,抛出一个 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 被赋予 added/subtracted.

如果您将第一个数字转换为 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

这里是没有使用任何 addition/subtraction 的证据。简单地将值转换为 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.MinValue:

的值溢出 int 时,会发生完全相反的情况
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.