天天看点

C++程序栈与尾递归优化

如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。  -----取自百度百科

只要理解了函数调用栈,那么尾递归就很好理解了。如果你还不理解什么叫函数调用栈

那么请看这篇博文。

接下来看一个典型的递归函数:求阶乘

C++程序栈与尾递归优化

rfact的帧如下图所示:(过程就是指函数)

C++程序栈与尾递归优化

这个递归函数rfact(n)由于语句n*rfact(n-1)的存在,在调用rfact(n-1)之后还要用到rfact(n)里的一个变量n,所以就必须在rfact(n)里保存n,所以就必须为rfact(n)保存一个帧,同理,必须为rfact(n-1),rfact(n-2)rfact(n-3)........rfact(1)保存一个帧。由此可以看出+++++++》如果n过大的话,那么保存的帧就会过多,而我们知道一个程序获得的程序栈大小是有限的,所以在运行过程中就很有可能会爆栈。。。爆栈。。。。。

让我们好好想一下:如果一个函数(调用者)在调用另一个函数(被调用者)时,调用者不需要保存任何变量,那么我们是不是就可以把被调用者的帧建立在调用者的帧上,即通过覆盖调用者的帧而不是开拓新的空间,来达到节省栈空间的目的呢,这样,不就大大降低了爆栈的几率么

说干就干,让我们改写上面的递归函数吧:

C++程序栈与尾递归优化

仔细观察上面的函数,我们会发现rfact(n)函数里并不需要保存什么变量,所以就没必要保存rfact(n)的帧,这里的关键就在于把结果当做一个参数传给rfact(n-1)所以我们可以通过覆盖调用者的帧来建立被调用者的帧,从而达到节约栈空间的目的。哈!这不就是所谓的尾递归吗(请回到这篇文章的开头好好体会一下,体会那种豁然开朗的感觉 )

也许你会问:我的上一篇博文里不是说了传递给被调用者的参数是存储在调用者的帧里么?

我的上篇博文里不是有esp ,ebp两个东东吗,你难道没有发现程序并没有分配空间来保存esp,ebp两个“指针么”。Esp,ebp就是寄存器之一,以前的cpu一共有八个寄存器,顾名思义,寄存器就是临时存放变量的东西,如果参数不是很多的情况下就可以将函数参数保存在寄存器里。现在的cpu寄存器翻倍了,有16个通用寄存器了,其中6个可以用来存放参数,所以你懂的,至于超过六个嘛,很抱歉,楼主对编译原理还不是很清楚,不知道写编译器的人会怎么处理(我估计还是能实现尾递归优化)我再给说一下:我们的程序栈是存放在存储区里(也就是内存)寄存器和内存是不同的两种缓冲区。

继续阅读