C#应用程序中的堆栈溢出

4

我对编程中的内存划分概念还不太了解。我发现在大多数.NET应用程序中,堆栈的大小通常是1MB。我的问题是:“当我在某些函数中使用本地变量为“Image”类型而其大小超过1MB时,为什么不会出现堆栈溢出?”提前致谢。

5个回答

7
因为StackOverflow异常与堆栈或堆内存管理无关。根据MSDN文档
引用:

当执行堆栈包含太多嵌套方法调用时,抛出此异常。该类不能被继承。

现在,如果你谈到的是内存中的stack,那么我们就进入了一个不同的世界。你在内存中存储的图像可能保存在大对象堆上。内存管理及其相关内容对于本论坛来说过于广泛,但如果您有关于内存管理的具体问题,我们可以解答。

重要的是你明白你在问题中混合了两个术语和概念,并且这两者之间有显著的区别。我不希望你继续认为由于大对象而应该得到 StackOverflow 异常。我也不希望你继续认为由于大对象和内存管理而导致了 StackOverflow 异常。


不,它与堆栈内存管理没有关系,但却是与一切有关。没有独立的栈用于本地变量和方法调用。 - Guffa
@Guffa,明确地说,StackOverflow异常与内存中的对象无关。您可以从MSDN文档中清楚地看到这一点。但是,如果OP在谈论内存管理,那就是一个不同的世界。请查看我的更新。 - Mike Perrenoud
@MichaelPerrenoud 这与内存管理有着密切关系。当堆栈耗尽内存时,就会出现 StackOverflowException 异常。理论上,您可能只有一个或两个方法调用包含 1MB 的堆栈空间,但实际上这几乎是不可能的;唯一的方法是通过大量嵌套的方法调用来实现。OP 没有意识到的是,他分配的大对象并没有放在堆栈上,堆栈只是指向堆上的对象(或在此情况下是大对象堆)的指针。 - Servy
@Servy,你的意思是说如果一个方法没有使用任何堆栈分配(例如没有本地变量),那么我可以递归调用同一个方法更多次,而如果它使用了堆栈分配,则不能? - Mike Perrenoud
1
@MichaelPerrenoud 是的,那肯定是这样。你可以创建一个包含几千个长整型的结构体,然后创建一个方法,其中包含几千个这些结构体(不是在数组中,而是在单独的命名变量中),从而更快地消耗堆栈。还有一种未管理的调用来获取堆栈内存,通过它,你可以在一个(非嵌套)方法调用中占用所有堆栈空间。 - Servy

6
图片本身并不存储在堆栈中,而是存储在堆中。只有指向/引用图片的指针/引用存储在堆栈中,这样更小。
public static void DoSomethingToImage()
{
    Image img = new Image(...);
}

在上面的代码片段中,图像是在堆上分配的,并且图像的引用存储在栈上的img变量中。

4

只有值类型的本地变量才会被分配在栈上。对于像Image这样的引用类型,只有引用被分配在栈上,对象被分配在堆上。


2

您的本地变量实际上是引用(“指针”)。这些图像并不存储在堆栈中 ;)


1
堆栈溢出错误发生的主要原因是函数调用次数过多,例如会导致此错误的示例如下:
static int x = 0;

static void Main()
{
    fn();
}

static void fn()
{
    Console.WriteLine(x++);
    fn();
}

这是由于代码出了问题,因为通常在数千次调用后才会发生这种情况。

上述应用程序以此方式退出:

...
15706
15707
15708
Process is terminated due to StackOverflowException.

您可以在调试器中学习查看“调用堆栈”窗口,它将显示函数调用列表。

这并不是严格的真实情况。虽然有可能,但是非常不寻常和不切实际,拥有一个消耗大量堆栈空间并可能因为过多嵌套调用而导致SOE的方法。在实践中,异常几乎总是由于这个原因,但这并不是唯一可能的原因。 - Servy
@Servy,我不确定这是否适用于托管的dotnet/c#,根据这里的文档:http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx,它只记录了嵌套函数调用,尝试在c#中声明一个数组,如`int[] i = new int[999888777];`会导致内存不足异常。..我已经编辑了我的帖子以防我错了。 - sharp12345
请注意,文档中说的是“通常”,而不是“”。这很重要。声明一个类似于你所拥有的数组将导致单个指针进入堆栈,其余部分进入堆(或在此情况下,大对象堆)。当然有可能会有一个方法占用大量的堆栈空间,但你不应该这样做。 - Servy
@Servy,我只是出于好奇做了一个小测试:static void fn2() { long x0 = 0 , x1 = 0 , .... , x100100 = 0 , x100101 = 0 ; }会产生错误:app1.cs(18,840840): error CS0204: Only 65534 locals are allowed,而这个:static void fn2() { long x0 = 0 , x1 = 0 , .... , x65528 = 0 , x65529 = 0 ; }和这个:static void fn2() { long x0 = 0 , x1 = 0 , .... , x65528 = 0 , x65529 = 0 ; Console.WriteLine(x++); fn2(); }永远不会溢出。...我认为C#有内部检查来防止堆栈溢出。 - sharp12345
65534个本地变量的限制实际上是出于完全不同的原因。如果您想要溢出,可能需要创建一个自定义结构,将像65528个长变量定义为不同的变量,然后具有其中65528个的方法。(在获得SOE之前,您实际上不需要那么多。) - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接