【问题标题】:Why doesn't this program overflow?为什么这个程序不会溢出?
【发布时间】:2017-06-24 19:19:01
【问题描述】:

为了研究 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# 甚至无法编译,因为代码可能会溢出。

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

【问题讨论】:

  • 您的一个场景是编译时常量,另一个不是。编译器可以阻止前者。
  • 阅读此处:docs.microsoft.com/en-us/dotnet/csharp/language-reference/… 在编译时处于选中模式,在运行时处于未选中模式。
  • 我认为第一种情况确实溢出,然后强制转换采用一个已经溢出的值(现在可能是负数)并将其强制转换为无符号,然后下溢并且该值变为最大 uint
  • 如果你在调试中运行它,你会得到一个 OverflowException。

标签: c# integer-overflow


【解决方案1】:

当你使用常量值时:

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

编译器确切地知道您要执行的操作,因为它知道值,并且它发现减法的结果不适合 int,因此它会给您错误。

当你使用变量时:

return (uint)(b - a);

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

请注意,溢出在int,而不是uint,正如您在现在已删除的答案中所述。您可能认为您正在从小价值中减去大价值,但事实并非如此。 int.MinValue 实际上是负数 (-2147483648),减去它意味着您实际上是在添加它 (2147483647 - (-2147483648)),所以结果 (4294967295) 不适合 int,但它可以适合 @987654330 @。例如,这将编译并给出正确的结果 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 被加/减。

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

【讨论】:

  • 我用值更新了答案,使其更清晰。 int.MaxValue的值是2147483647,int.MinValue的值是-2147483648(注意是负数),所以减法结果2147483647 - (-2147483648)是4294967295,不适合int。跨度>
  • 这取决于上下文。当您使用常量值时,编译器甚至不会像您说的那样编译。当你使用一个函数时,编译器会放开它,然后它取决于上下文。如果您在检查模式下运行代码,则在 int 中会发生运行时溢出异常,并且永远不会达到 uint 强制转换。如果您在未经检查的模式下运行代码,那么您是对的,您将有两次溢出。检查此MSDN 以了解整数溢出的行为。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-10
  • 2015-12-09
相关资源
最近更新 更多