为什么这个程序不会溢出?
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.
为了研究 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.