繁体   English   中英

为什么这段代码会阻止gcc&llvm进行尾调优化?

[英]Why does this code prevent gcc & llvm from tail-call optimization?

我曾尝试在GCC 4.4.5在Linux在Mac OSX(4.2.1的Xcode)和下面的代码和gcc-LLVM 以下是相关功能的来源和生成的反汇编。 (补充:使用gcc -O2 main.c编译)

#include <stdio.h>
__attribute__((noinline))
static void g(long num)
{
        long m, n;
        printf("%p %ld\n", &m, n);
        return g(num-1);
}
__attribute__((noinline))
static void h(long num)
{
        long m, n;
        printf("%ld %ld\n", m, n);
        return h(num-1);
}
__attribute__((noinline))
static void f(long * num)
{
        scanf("%ld", num);
        g(*num);
        h(*num);        
        return f(num);
}
int main(void)
{
        printf("int:%lu long:%lu unsigned:%lu\n", sizeof(int), sizeof(long), sizeof(unsigned));
        long num;
        f(&num);                 
        return 0;
}

08048430 <g>:
8048430:    55                   push   %ebp
8048431:    89 e5                mov    %esp,%ebp
8048433:    53                   push   %ebx
8048434:    89 c3                mov    %eax,%ebx
8048436:    83 ec 24             sub    $0x24,%esp
8048439:    8d 45 f4             lea    -0xc(%ebp),%eax
804843c:    c7 44 24 08 00 00 00 movl   $0x0,0x8(%esp)
8048443:    00 
8048444:    89 44 24 04          mov    %eax,0x4(%esp)
8048448:    c7 04 24 d0 85 04 08 movl   $0x80485d0,(%esp)
804844f:    e8 f0 fe ff ff       call   8048344 <printf@plt>
8048454:    8d 43 ff             lea    -0x1(%ebx),%eax
8048457:    e8 d4 ff ff ff       call   8048430 <g>
804845c:    83 c4 24             add    $0x24,%esp
804845f:    5b                   pop    %ebx
8048460:    5d                   pop    %ebp
8048461:    c3                   ret    
8048462:    8d b4 26 00 00 00 00 lea    0x0(%esi,%eiz,1),%esi
8048469:    8d bc 27 00 00 00 00 lea    0x0(%edi,%eiz,1),%edi

08048470 <h>:
8048470:    55                   push   %ebp
8048471:    89 e5                mov    %esp,%ebp
8048473:    83 ec 18             sub    $0x18,%esp
8048476:    66 90                xchg   %ax,%ax
8048478:    c7 44 24 08 00 00 00 movl   $0x0,0x8(%esp)
804847f:    00 
8048480:    c7 44 24 04 00 00 00 movl   $0x0,0x4(%esp)
8048487:    00 
8048488:    c7 04 24 d8 85 04 08 movl   $0x80485d8,(%esp)
804848f:    e8 b0 fe ff ff       call   8048344 <printf@plt>
8048494:    eb e2                jmp    8048478 <h+0x8>
8048496:    8d 76 00             lea    0x0(%esi),%esi
8048499:    8d bc 27 00 00 00 00 lea    0x0(%edi,%eiz,1),%edi

080484a0 <f>:
80484a0:    55                   push   %ebp
80484a1:    89 e5                mov    %esp,%ebp
80484a3:    53                   push   %ebx
80484a4:    89 c3                mov    %eax,%ebx
80484a6:    83 ec 14             sub    $0x14,%esp
80484a9:    8d b4 26 00 00 00 00 lea    0x0(%esi,%eiz,1),%esi
80484b0:    89 5c 24 04          mov    %ebx,0x4(%esp)
80484b4:    c7 04 24 e1 85 04 08 movl   $0x80485e1,(%esp)
80484bb:    e8 94 fe ff ff       call   8048354 <__isoc99_scanf@plt>
80484c0:    8b 03                mov    (%ebx),%eax
80484c2:    e8 69 ff ff ff       call   8048430 <g>
80484c7:    8b 03                mov    (%ebx),%eax
80484c9:    e8 a2 ff ff ff       call   8048470 <h>
80484ce:    eb e0                jmp    80484b0 <f+0x10>

我们可以看到除了printf()的参数m旁边的& (地址)运算符(以及无关的%ld%p )之外, g()h()大部分都是相同的。 但是, h()是尾调用优化而g()不是。 为什么?

在g()中,您将获取局部变量的地址并将其传递给函数。 “足够智能的编译器”应该意识到printf不存储该指针。 相反,gcc和llvm假设printf可能将指针存储在某处,因此包含m的调用框架可能需要在递归中进一步“生存”。 因此,没有TCO。

这就是&它做到了。 它告诉编译器m应该存储在堆栈中。 即使它被传递给printf ,编译器也必须假设它可能被其他人访问,因此必须在调用g之后从堆栈中清除它。

在这种特殊情况下,由于编译器已知printf (并且它知道它不保存指针),因此可能会教它执行此优化。

有关这方面的更多信息,请查看“逃避分析”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM