【问题标题】:How to use inline with recursion?如何使用递归内联?
【发布时间】:2018-02-03 10:56:11
【问题描述】:

来自Programming Language Pragmatics, by Scott,关于内联和递归:

在一般情况下,内联扩展也不是一个选项 递归子程序。对于递归的偶尔情况 调用是可能的,但不太可能,可能需要生成一个真实的 递归子例程,但扩展该例程的一级 在每个呼叫站点在线

作为一个简单的例子,考虑一棵二叉树,它的叶子包含 字符串。返回这棵树的边缘的例程( 从左到右连接其叶子中的值)可能看起来 像这样在 C++ 中:

string fringe(bin_tree *t) {
    // assume both children are nil or neither is
    if (t->left == 0) return t->val;
    return fringe(t->left) + fringe(t->right);
}

如果编译器使每个嵌套调用成为真正的子例程调用,则编译器可以内联扩展此代码。由于二进制中的一半节点 树是叶子,这种扩展将消除一半的动态调用 在运行时。

如果我们不仅扩展根调用,而且扩展(一层)这两个 在真正的子程序版本中调用,只有四分之一 原始动态调用将保留。

我无法理解以下句子:

  • “在每个呼叫站点在线扩展该例程的一级”
  • “如果此代码使每个嵌套调用成为真正的子例程调用,则行内扩展此代码。”
  • “在真正的子程序版本中,不仅扩展根调用,而且扩展两个调用(一层)”

它们实际上是什么意思?你能用给定的例子来解释它们吗,例如,展示每个句子的动作后的代码是什么样的?

谢谢。

【问题讨论】:

  • 代码完全一样,但编译器可以决定内联一次调用,并保留同一函数的非内联版本。

标签: c++ recursion programming-languages inline computer-science


【解决方案1】:

到目前为止,理解这一点的最简单方法是将内联视为创建另一种二进制表示。例如。除了创建基本的string fringe(bin_tree *t),编译器还可能决定创建string fringe__inlined(bin_tree *t)。而在fringe__inlined 中,实际的内联函数将是fringe 的一两个副本。

甚至可以通过这种方式创建fringe__inlined__inlined(如前所述,分为两个级别)。

【讨论】:

    【解决方案2】:

    一些编译器可以扩展和内联非常嵌套的递归调用。

    例如下面的代码(这里我没有使用inline关键字!):

    static int fact(int n) {
        if (n<=1) return 1;
        else return n* fact(n-1);
    }
    
    extern "C" int f5();
    int f5() {
        return fact(5);
    }    
    

    GCC 7.2(在 x86-64 上的 Linux/Debian/Sid 上)编译(使用 g++ -O2 -fverbose-asm -S)成一个返回 120 的简单函数:

             .text
             .p2align 4,,15
             .globl  f5
             .type   f5, @function
     f5:
     .LFB1:
             .cfi_startproc
     # e.cc:10: };
             movl    $120, %eax      #,
             ret
             .cfi_endproc
     .LFE1:
             .size   f5, .-f5
             .ident  "GCC: (Debian 7.2.0-1) 7.2.0"
             .section        .note.GNU-stack,"",@progbits
    

    请注意,fact 已完全内联,不会出现在生成的汇编代码中。

    “在每个呼叫站点在线扩展该例程的一个级别”

    这意味着f5 将被编译成等价于

    int f5() { return 5*fact(4); }
    

    fact 出现在生成的代码中并编译成递归(机器代码)函数(消耗调用堆栈)。

    【讨论】:

      【解决方案3】:

      你能用给定的例子解释一下吗,例如,显示代码是什么样的

      注意:编译器/优化器不会对您编写的 C++ 代码执行这些操作,而是在某些内部表示上执行这些操作。可以用 C++ 来演示这个想法,但由于语法和可读性问题,我们可能需要引入额外的更改,例如临时变量。

      在每个呼叫站点在线扩展该例程的一个级别。

      result = fringe(t);
      

      变成

      if (t->left == 0) result = t->val;
      else result = fringe(t->left) + fringe(t->right);
      

      在真正的子程序版本中,不仅扩展根调用,而且扩展(一层)两个调用

      if (t->left == 0) result = t->val;
      else {
          string left, right;
      
          // one level of expansion for left subtree
          if (t->left->left == 0) left = t->left->val;
          else left = fringe(t->left->left) + fringe(t->left->right);
      
          // one level of expansion for right subtree
          if (t->right->left == 0) right = t->right->val;
          else right = fringe(t->right->left) + fringe(t->right->right);
      
          result = left + right;
      }
      

      【讨论】:

        猜你喜欢
        • 2014-03-02
        • 1970-01-01
        • 2018-02-08
        • 1970-01-01
        • 2010-09-16
        • 2011-12-31
        • 2010-10-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多