递归调用和栈溢出
一、简介
栈溢出:Stack Overflow。对于每个程序,栈能使用的内存是有限的,一般是1M-8M,在程序编译时就已经决定了,程序运行期间不能改变。如果程序使用的栈内存超出最大值,就会发生栈溢出错误,程序会崩溃。
二、栈溢出的原因
因为每调用一个方法就会在栈上创建一个栈帧,方法调用结束后就会弹出该栈帧,而栈的大小不是无限的,所以递归调用次数过多的话就会导致溢出。而递归调用的特点是每递归一次,就要创建一个新的栈帧,而且还要保留之前的环境(栈帧),直到遇到结束条件。所以递归调用一定要明确好结束条件,不要出现死循环,而且避免栈太深。
三、解决办法
- 不使用递归,用循环替代。缺点是代码逻辑不够清晰。
- 限制递归次数。
- 使用尾递归。尾递归指在方法返回时,只调用自己本身,且不能包含表达式。编译器或解释器把尾递归做优化,使递归方法无论调用多少次,都只会占用一个栈帧,所以不会出现栈溢出。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:尾递归自己并不会销毁栈帧,而是依赖于程序有没有编译器的优化。如果不开优化,依然可能出现栈溢出。所以主要取决于编译器。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南