栈溢出是什么?

36

什么是堆栈溢出错误?它很可能在哪些程序/编程语言中发生?在Web应用程序代码中发生的可能性不大吗?


http://en.wikipedia.org/wiki/Stack_overflow - David Basarab
7个回答

26

来自维基百科

在软件中,当调用堆栈使用过多内存时,就会发生堆栈溢出。在许多编程语言中,调用堆栈包含有限数量的内存,通常在程序开始时确定。

堆栈是一种数据结构,记录了程序子例程执行完成后应返回控制权的位置。随着调用子例程的推进,返回地址会被压入堆栈,当子例程执行完成后,返回地址将从堆栈中被弹出。如果存在许多子例程且堆栈没有足够的空间,则会发生堆栈溢出。

堆栈还用于存储局部变量,因此如果局部变量太大,则堆栈不足以存储它,这也会导致堆栈溢出。

维基百科包含了一个很好的图示,描述了当从名为DrawSquare的另一个子例程中调用DrawLine子例程时堆栈的情况,我希望这张图片能帮助更好地理解堆栈结构。

堆栈图示

堆栈溢出的两个主要原因是:函数递归过深堆栈变量过大。由于这些在几乎所有编程语言中都是常见术语,除了语言的复杂性之外,堆栈溢出也可能会发生。

Guffa贡献:堆栈与垃圾收集无关。现代应用程序具有较大的堆栈,这使得堆栈溢出的可能性稍微降低了一些,但除此之外并没有什么区别。


1
当然,这在脚本语言中是可能发生的。它们有一个调用堆栈,自然会溢出。 - Guffa
1
是的,在Java中可能会发生这种情况,例如在使用非常深层的递归时。 - victor hugo
5
编写一个没有良好退出条件的递归函数,在任何一种编程语言中都会导致栈溢出,无论是否有垃圾回收机制。 - Fredrik Mörk
3
栈和垃圾收集没有任何关系。现代应用程序的栈更大,这使得发生栈溢出的可能性稍微降低了一些,但除此之外没有任何区别。 - Guffa
2
@Fredrik Mörk - 嗯,除非它是尾递归的并且你的语言支持尾调用优化。;) - Edward Kmett
显示剩余2条评论

18

堆栈包含多个堆栈帧并存储在内存中。每次调用函数时,都会向堆栈添加一个新的堆栈帧。堆栈帧包含要传递给被调用函数的参数和返回地址,以便在被调用函数完成后,cpu 知道应该返回哪里,以便继续执行调用函数。堆栈帧还可以包含被调用函数的局部变量可以使用的内存。

在这个例子中,Main 函数调用了 WriteCustomerDetails,并且该函数又调用 PrintToConsole 来写出 WriteCustomerDetails 查找到的每个数据位:

'======= 堆栈顶部 ====================='
函数:PrintToConsole
参数:John Smith、34 Acacia Avenue、年龄 23
'-----------------------------------------------------------'
函数:WriteCustomerDetails
参数:John Smith
'-----------------------------------------------------------'
函数:Main
'====== 堆栈底部 ===================='

如果没有为堆栈预留足够的空间,则会发生堆栈溢出。通常,堆栈位于一个大的连续内存块中,因此不会被分成多个块,这意味着需要一个大内存块来存储它,这使运行时难以尝试增加为其保留的堆栈空间,如果它填满了。

堆栈溢出通常会在意外编写调用自身的函数时发生。有时,允许函数调用自身,只要函数中有一个 "if "或一些条件来停止调用。这称为递归函数。但是,如果没有停止并且函数不断调用自身,或者可能两个或更多函数互相调用,那么它们将非常快地耗尽所有堆栈内存。当没有剩余时,就会发生堆栈溢出并导致程序崩溃。

这种情况可能发生在任何程序中,它们不一定需要很复杂,并且可能会发生在运行网站的代码中。此外,它也可能发生在脚本语言中。


7

当你使用太多的堆栈空间时,就会发生堆栈溢出。通常有两种情况会导致这种情况发生:

第一种情况是代码中存在错误,导致递归循环没有退出。例如,一个属性从自身读取:

public int Length {
   get {
      return Length;
   }
}

第二种情况是当你有一个递归循环太深时。由于堆栈空间有限,你只能嵌套算法一定次数。如果你的算法嵌套过深,以至于在它退出之前耗尽了堆栈空间,就会发生堆栈溢出。例如:
public bool Odd(int value) {
   if (value == 0) {
      return false;
   } else {
      return !Odd(value - 1);
   }
}

如果您使用过大的值调用此方法,它将嵌套得太深,导致堆栈溢出。

4
您提供的两个例子都因为递归而导致了堆栈溢出。不过,还有另一个相当简单的原因:如果在堆栈上分配的(本地)变量或函数参数太大,通常会发生这种情况,尤其是数组,详见http://en.wikipedia.org/wiki/Stack_overflow。请注意,我只翻译内容,不包括解释或其他信息。 - Dirk Vollmar

6

来自维基百科

在软件中,当调用栈上使用了过多的内存时,会发生堆栈溢出。在许多编程语言中,调用栈包含有限的内存,通常在程序开始时确定。调用栈的大小取决于许多因素,包括编程语言、机器架构、多线程和可用内存量。当调用栈上使用了过多的内存时,就会导致堆栈溢出;通常会导致程序崩溃。1 这种软件错误通常由两种类型的编程错误之一引起。


1
这不是我要找的答案。 - SREE

1

当你使用堆栈(duh...)并且存在内存分配/读取问题时,就会发生堆栈溢出。在“Web程序”中,正如你所说的(我假设你是在谈论HTML、PHP、JS),要么你不使用堆栈,要么所使用的语言不允许进行低级内存控制,从而避免这些问题。


2
缺乏对内存分配的控制并不会防止堆栈溢出。 - Dirk Vollmar
2
几乎每种编程语言都有一个调用堆栈,它是必需的,以便代码在子程序结束后返回到原来的位置。这个调用堆栈通常有一个固定的大小,因此如果调用太多的子程序而没有返回,堆栈就会变满并溢出。 - Kevin Panko

0

当分配给堆栈的内存耗尽时,系统将抛出堆栈溢出错误。

注意: 堆栈是一种只允许推入和弹出的内存。 您无法访问其中的值。 它遵循后进先出的原则。


0

当递归方法调用没有基础/终止条件时,堆栈逻辑结构过度充满时会发生堆栈溢出。在典型的程序中,您的原始变量(例如整数、浮点数等)存储在物理内存中,而您的方法调用存储在逻辑数据结构(如堆栈)中。堆栈以后进先出的顺序(LIFO)存储数据。

Main(){ 
 foo(3); 
 } 

 foo(n){
  if(n<1) // recursion with base condition that terminates when n<1
   return;
  else foo(n-1); 
  print ('Hello' + n);
 }

enter image description here

如果没有基本条件if(n<1) return,方法foo(n)将递归调用自身,直到堆栈中没有更多空间,因此会出现堆栈溢出。


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