递归调用和栈溢出

一、简介

       栈溢出:Stack Overflow。对于每个程序,栈能使用的内存是有限的,一般是1M-8M,在程序编译时就已经决定了,程序运行期间不能改变。如果程序使用的栈内存超出最大值,就会发生栈溢出错误,程序会崩溃。

二、栈溢出的原因

       因为每调用一个方法就会在栈上创建一个栈帧,方法调用结束后就会弹出该栈帧,而栈的大小不是无限的,所以递归调用次数过多的话就会导致溢出。而递归调用的特点是每递归一次,就要创建一个新的栈帧,而且还要保留之前的环境(栈帧),直到遇到结束条件。所以递归调用一定要明确好结束条件,不要出现死循环,而且避免栈太深。

三、解决办法

  1. 不使用递归,用循环替代。缺点是代码逻辑不够清晰。
  2. 限制递归次数。
  3. 使用尾递归。尾递归指在方法返回时,只调用自己本身,且不能包含表达式。编译器或解释器把尾递归做优化,使递归方法无论调用多少次,都只会占用一个栈帧,所以不会出现栈溢出。Tips:Java没有尾递归优化。       

四、尾递归例子

       一般的递归code:     

fuction add(num){
     if(num == 1) return 1;
     return num + add(num - 1);
}

     尾递归code:需要添加一个辅助变量,这里我们用sum

///
/// 尾递归:函数结束只能调用自身
///
function add(num,sum){
    if(num == 1) return sum + num;
    return add(num - 1,sum + num);
}

      分析:一般递归中,add(num)需要在得到 add(num - 1)结果之后才会计算它自己的返回值,所以理论上,在add(num - 1)返回之前,add(num)不能返回结果,所以add(num)在栈上额数据必须保留,知道add(num - 1)先返回,而栈的大小不是无限的,windows下为1M。所以可能出现栈溢出。

     尾递归中,它不必等拿到 add(num - 1,sum + num)的返回值才计算自己的结果,完全可以等于add(num - 1,sum + num)的返回值。理论上 add(num,sum) 在调用 add(num - 1,sum + num)之前,完全可以先销毁自己的栈帧。这也就是为何尾递归在得到编译器的帮助下,完全可以避免栈溢出的原因。每个函数在调用下一个函数之前,都能做法哦先把当前自己的栈帧释放。尾递归的调用链上可以做到只有一个函数在使用栈,因此可以无限调用。

     Tips:尾递归自己并不会销毁栈帧,而是依赖于程序有没有编译器的优化。如果不开优化,依然可能出现栈溢出。所以主要取决于编译器。

 

posted on 2024-02-20 14:43  木乃伊人  阅读(102)  评论(0编辑  收藏  举报

导航