当函数在最后一行或使用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)
为什么优化不强制弹出父级?
根据评论:该主题以“尾递归”而闻名。
答案 0 :(得分:1)
我已经将它扔进godbolt
在具有-O3
优化的gcc(8.3)和clang(8.0.0)上,该函数编译为无操作函数。令人印象深刻的是,对于具有/O2
优化的MSVC v19.20甚至如此。
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 << "hello\n";
if (i > 0)
a(i - 1);
}
启用了-O3
优化的gcc trunc的编译器输出如下:
.LC0:
.string "hello\n"
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
指令执行的,因为(如您所说)先前的执行信息是无关紧要的。