递归调用层数太多

您所在的位置:网站首页 递归函数流程图 递归调用层数太多

递归调用层数太多

2023-09-02 13:51| 来源: 网络整理| 查看: 265

eb33ea7114af22f387022d59dfbf25bb.gif

db3faab82f7fff7f90ca59f7ce65834f.png

作者 | Cooper Song 责编 | Elle 出品 | 程序人生(ID:coder_life)

我猜,大多数程序员第一次接触函数的递归调用都是在算斐波那契数列某项值的时候,这是函数递归调用最常见的应用之一。规定第一项和第二项为1,后面的项,每一项都是其前面两项的和。

用公式表示就是f(n)=f(n-2)+f(n-1)。

而进一步转化,就是f(n)=[f(n-2-2)+f(n-2-1)]+[f(n-1-2)+f(n-1-1)]。

很明显,这是一个递归的过程。

递归的优点是算法简单、容易理解,代码行数少。

但递归也有缺点,咱们将上面的f(n)再化简一下就变成了f(n)=[f(n-4)+f(n-3)]+[f(n-3)+f(n-2)],可以看出,f(n-3)被计算了两次,而f(n-4)+f(n-3)就是再计算f(n-2),又与最后一项f(n-2)是一样的,f(n-2)也被重复计算了。因此,递归的一大缺点就是存在大量的重复计算,运行起来浪费时间也浪费空间。

递归的另一个缺点是递归的层数不能太多(不能递归太深)。那递归得太深了会怎样呢?答案是会爆栈。那么什么是爆栈呢?又是怎样引发爆栈的呢?下面就要从最底层的角度讲一讲函数调用及函数递归调用的原理,相信读完了就会找到答案。

这就要先从程序的链接和装入说起了。

93e690b10fc32c9546c065cc86aa9a16.png 程序的链接(Link)

一个程序是由多个模块构成的,以C语言为例,有头文件,只有引用了这个头文件你才能使用scanf和printf;还有头文件,只有引用了这个头文件你才能直接调用strlen函数得到字符串的大小。所谓程序的链接,就是将整个程序的所有目标模块(比如程序员自己写的头文件和函数)以及其他所需要的库函数装配成一个完整的装入模块。

原来每个模块都有每个模块的逻辑地址,经过链接后,形成了统一的从0开始的逻辑地址,如下图所示。

34851fc304ff6e0abd5b9951bba466f4.png

如何理解模块?看上图大概就有了概念,一个函数就是一个模块。

a4f420284594f04fe5cbbd65c17f9320.png 程序的装入(Load)

学过计算机组成原理的同学都知道,在计算机中有个部件叫程序计数器(Program Counter,简称PC),它存放的是程序要执行的下一条指令的地址,CPU要到内存当中去取指令,取到CPU中进行译码分析然后执行。

程序原本存储在磁盘上,因此只经过链接还不能运行,还需要装入主存(内存),CPU通过PC提供的线索到内存中去取指令,如此循环往复,程序才得以运行下去。虽然程序的第一条指令的逻辑地址是0,但它装入内存时在内存中的地址可不是0,因为内存中的低地址是留给系统使用的,也就是系统区,比系统区的地址高的空间才是留给用户使用的,也就是用户区。虽然装入内存后其地址不再是从0开始,但其相对地址是不变的,将上面链接好的装入模块装入内存,内存空间示意图如下。

5b3b510e72121264467ffc8db633b52f.png

526f78446de0b0a0981c723e7ff1d9b5.png 函数的调用

所谓函数的调用,就是程序原本在主模块中顺序执行,遇到调用指令暂时到别的模块执行,在别的模块执行完后再返回主模块的下一条指令继续执行,如下图所示。

029d0b7ed281f747827ea7730a5e8540.png

为什么可以执行着执行着就跳到别的模块执行了?又为什么在别的模块执行完了又回到原来的模块执行了呢?之所以能跳到别的模块执行,是因为函数调用指令就指明了目标模块的首地址,将目标模块的首地址传送给了程序计数器PC,就中断了程序的顺序执行,



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3