如何防止递归功能调用(尾部回复)堆积在堆栈中

How to prevent recursive function call (tail-recursion) from piling up in stack?

本文关键字:堆栈 回复 尾部 递归 何防止 功能 调用      更新时间:2023-10-16

当函数在最后一行或使用return命令中调用自己时,似乎不必将呼叫者放在堆栈中。

我用" GCC"测试了这一理论,发现呼叫者函数仍在堆栈中:

#include <iostream>
void a(int i)
{
    std::cout << i << std::endl;
    if (i > 0)
        a(i - 1);
    // Also tested return a(i - 1);
}
int main()
{
    a(10);
}

呼叫堆栈:

...
a(int i) (/mnt/temp/hackerrank/src/main.cpp:30)
a(int i) (/mnt/temp/hackerrank/src/main.cpp:30)
a(int i) (/mnt/temp/hackerrank/src/main.cpp:30)
main() (/mnt/temp/hackerrank/src/main.cpp:35)

为什么优化不迫使父母弹出?

根据评论:此主题以"尾部递归"而闻名。

我把它扔进了Godbolt

在GCC(8.3(和Clang(8.0.0(上具有-O3优化,该功能将其编译为NO-OP函数。令人印象深刻的是,对于MSVC v19.20而言,这甚至是/O2优化也是如此。

GCC 8.3/clang 8.0.0:

a(int):
        ret

msvc v19.20(x64(:

i$ = 8
void a(int) PROC                               ; a, COMDAT
        ret     0
void a(int) ENDP                               ; a

我也以使榜样非平凡的自由。我从这里使用的代码是:

#include <iostream>
void a(int i)
{
    std::cout << "hellon";
    if (i > 0)
        a(i - 1);
}

启用-O3优化的GCC截面的编译器输出以下是:

.LC0:
        .string "hellon"
a(int):
        push    rbx
        mov     ebx, edi
        jmp     .L3
.L6:
        sub     ebx, 1
.L3:
        mov     edx, 6
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        test    ebx, ebx
        jg      .L6
        pop     rbx
        ret
_GLOBAL__sub_I_a(int):
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

从仔细检查中,唯一的call指令是在IO方法上写下每次迭代中的消息的方法。然后执行test(if语句(。如果i > 0,控件会跳起来,将减少i,然后再次完成。if语句(错误情况(的另一个分支简单地返回(堆栈清理后(。

因此,即使在这个非平凡的示例中,也没有堆叠框架的积累。它是通过jmp指令执行的,因为(正如您所说的(以前的执行信息无关。