【问题标题】:What is a trampoline function?什么是蹦床功能?
【发布时间】:2010-09-16 10:13:25
【问题描述】:

在最近的工作讨论中,有人提到了蹦床功能。

我已阅读Wikipedia 的说明。对功能有一个大致的了解就足够了,但我想要一些更具体的东西。

你有一个简单的 sn-p 代码来说明蹦床吗?

【问题讨论】:

  • 在微软的世界里,蹦床通常被称为“thunk”。 [这是一页][1] 来自 Andrei Alexandrescu 的《现代 C++ 设计》---- [1]:books.google.com/…
  • 它基本上是您可以使用 setjmp/lomgjmp 实现的某些功能的通用形式,即避免堆栈 ovwerflow。
  • 为什么有人要避免stackoverflow?

标签: c language-agnostic programming-languages trampolines


【解决方案1】:

还有 LISP 对“蹦床”的理解,如 Wikipedia 上所述:

在一些 LISP 实现中使用,a 蹦床是一个循环迭代 调用 thunk-returning 函数。一种 单人蹦床就足够了 表达所有控制权转移 程序;这样表达的程序是 蹦床或“蹦床式”; 将程序转换为蹦床 风格是蹦床。蹦床 函数可以用来实现 尾递归函数调用 面向堆栈的语言

假设我们正在使用 Javascript,并且想要以连续传递样式编写简单的斐波那契函数。我们这样做的原因无关紧要——例如将 Scheme 移植到 JS,或者使用 CPS,无论如何我们都必须使用它来调用服务器端函数。

所以,第一次尝试是

function fibcps(n, c) {
    if (n <= 1) {
        c(n);
    } else {
        fibcps(n - 1, function (x) {
            fibcps(n - 2, function (y) {
                c(x + y)
            })
        });
    }
}

但是,在 Firefox 中使用 n = 25 运行它会出现错误“递归过多!”。现在这正是蹦床解决的问题(Javascript 中缺少尾调用优化)。与其对函数进行(递归)调用,不如让我们return 一条指令(thunk)来调用该函数,并在循环中进行解释。

function fibt(n, c) {
    function trampoline(x) {
        while (x && x.func) {
            x = x.func.apply(null, x.args);
        }
    }

    function fibtramp(n, c) {
        if (n <= 1) {
            return {func: c, args: [n]};
        } else {
            return {
                func: fibtramp,
                args: [n - 1,
                    function (x) {
                        return {
                            func: fibtramp,
                            args: [n - 2, function (y) {
                                return {func: c, args: [x + y]}
                            }]
                        }
                    }
                ]
            }
        }
    }

    trampoline({func: fibtramp, args: [n, c]});
}

【讨论】:

    【解决方案2】:

    让我添加几个使用不同语言的蹦床实现的阶乘函数示例:

    斯卡拉:

    sealed trait Bounce[A]
    case class Done[A](result: A) extends Bounce[A]
    case class Call[A](thunk: () => Bounce[A]) extends Bounce[A]
    
    def trampoline[A](bounce: Bounce[A]): A = bounce match {
      case Call(thunk) => trampoline(thunk())
      case Done(x) => x
    }
    
    def factorial(n: Int, product: BigInt): Bounce[BigInt] = {
        if (n <= 2) Done(product)
        else Call(() => factorial(n - 1, n * product))
    }
    
    object Factorial extends Application {
        println(trampoline(factorial(100000, 1)))
    }
    

    Java:

    import java.math.BigInteger;
    
    class Trampoline<T> 
    {
        public T get() { return null; }
        public Trampoline<T>  run() { return null; }
    
        T execute() {
            Trampoline<T>  trampoline = this;
    
            while (trampoline.get() == null) {
                trampoline = trampoline.run();
            }
    
            return trampoline.get();
        }
    }
    
    public class Factorial
    {
        public static Trampoline<BigInteger> factorial(final int n, final BigInteger product)
        {
            if(n <= 1) {
                return new Trampoline<BigInteger>() { public BigInteger get() { return product; } };
            }   
            else {
                return new Trampoline<BigInteger>() { 
                    public Trampoline<BigInteger> run() { 
                        return factorial(n - 1, product.multiply(BigInteger.valueOf(n)));
                    } 
                };
            }
        }
    
        public static void main( String [ ] args )
        {
            System.out.println(factorial(100000, BigInteger.ONE).execute());
        }
    }
    

    C(不幸没有大数字实现):

    #include <stdio.h>
    
    typedef struct _trampoline_data {
      void(*callback)(struct _trampoline_data*);
      void* parameters;
    } trampoline_data;
    
    void trampoline(trampoline_data* data) {
      while(data->callback != NULL)
        data->callback(data);
    }
    
    //-----------------------------------------
    
    typedef struct _factorialParameters {
      int n;
      int product;
    } factorialParameters;
    
    void factorial(trampoline_data* data) {
      factorialParameters* parameters = (factorialParameters*) data->parameters;
    
      if (parameters->n <= 1) {
        data->callback = NULL;
      }
      else {
        parameters->product *= parameters->n;
        parameters->n--;
      }
    }
    
    int main() {
      factorialParameters params = {5, 1};
      trampoline_data t = {&factorial, &params};
    
      trampoline(&t);
      printf("\n%d\n", params.product);
    
      return 0;
    }
    

    【讨论】:

    • 您的解释,尤其是C示例,以及下面关于嵌套函数的ephemient的回答,终于让我理解了蹦床。一种辅助函数,可用于像闭包一样更新状态。
    • Scala 代码应该更正为 if (n &lt; 2) Done(product),所以不允许我编辑 1 个符号...
    【解决方案3】:

    我给你举个例子,我在一个在线游戏的反作弊补丁中使用过。

    我需要能够扫描游戏加载的所有文件以进行修改。因此,我发现最可靠的方法是对 CreateFileA 使用蹦床。所以当游戏启动时,我会使用 GetProcAddress 找到 CreateFileA 的地址,然后我会修改函数的前几个字节并插入会跳转到我自己的“trampoline”函数的汇编代码,在那里我会做一些事情,并且然后我会在我的 jmp 代码之后跳回到 CreateFile 中的下一个位置。能够可靠地做到这一点比这要复杂一些,但基本概念只是挂钩一个函数,强制它重定向到另一个函数,然后跳回原来的函数。

    编辑:微软有一个你可以查看的此类事物的框架。叫Detours

    【讨论】:

      【解决方案4】:

      我目前正在尝试为 Scheme 解释器实现尾调用优化的方法,因此目前我正在尝试弄清楚蹦床对我来说是否可行。

      据我了解,它基本上只是由蹦床函数执行的一系列函数调用。每个函数都称为 thunk,并返回计算的下一步,直到程序终止(空继续)。

      这是我为加深对蹦床的理解而编写的第一段代码:

      #include <stdio.h>
      
      typedef void *(*CONTINUATION)(int);
      
      void trampoline(CONTINUATION cont)
      {
        int counter = 0;
        CONTINUATION currentCont = cont;
        while (currentCont != NULL) {
          currentCont = (CONTINUATION) currentCont(counter);
          counter++;
        }
        printf("got off the trampoline - happy happy joy joy !\n");
      }
      
      void *thunk3(int param)
      {
        printf("*boing* last thunk\n");
        return NULL;
      }
      
      void *thunk2(int param)
      {
        printf("*boing* thunk 2\n");
        return thunk3;
      }
      
      void *thunk1(int param)
      {
        printf("*boing* thunk 1\n");
        return thunk2;
      }
      
      int main(int argc, char **argv)
      {
        trampoline(thunk1);
      }
      

      结果:

      meincompi $ ./trampoline 
      *boing* thunk 1
      *boing* thunk 2
      *boing* last thunk
      got off the trampoline - happy happy joy joy !
      

      【讨论】:

        【解决方案5】:

        这是一个嵌套函数的例子:

        #include <stdlib.h>
        #include <string.h>
        /* sort an array, starting at address `base`,
         * containing `nmemb` members, separated by `size`,
         * comparing on the first `nbytes` only. */
        void sort_bytes(void *base,  size_t nmemb, size_t size, size_t nbytes) {
            int compar(const void *a, const void *b) {
                return memcmp(a, b, nbytes);
            }
            qsort(base, nmemb, size, compar);
        }
        

        compar 不能是外部函数,因为它使用了nbytes,它只存在于sort_bytes 调用期间。在某些架构上,一个小的存根函数——蹦床——是在运行时生成的,它包含sort_bytes当前 调用的堆栈位置。调用时,它会跳转到compar 代码,并传递该地址。

        像 PowerPC 这样的架构不需要这种混乱,其中 ABI 指定函数指针实际上是一个“胖指针”,一个包含指向可执行代码的指针和另一个指向数据的指针的结构。但是,在 x86 上,函数指针只是一个指针。

        【讨论】:

          【解决方案6】:

          对于 C,蹦床将是一个函数指针:

          size_t (*trampoline_example)(const char *, const char *);
          trampoline_example= strcspn;
          size_t result_1= trampoline_example("xyzbxz", "abc");
          
          trampoline_example= strspn;
          size_t result_2= trampoline_example("xyzbxz", "abc");
          

          编辑:编译器会隐式生成更深奥的蹦床。一种这样的用途是跳转表。 (尽管越往下你开始尝试生成复杂的代码,显然就会越复杂。)

          【讨论】:

            【解决方案7】:

            既然 C# 有 Local FunctionsBowling Game coding kata 可以用蹦床优雅地解决:

            using System.Collections.Generic;
            using System.Linq;
            
            class Game
            {
                internal static int RollMany(params int[] rs) 
                {
                    return Trampoline(1, 0, rs.ToList());
            
                    int Trampoline(int frame, int rsf, IEnumerable<int> rs) =>
                          frame == 11             ? rsf
                        : rs.Count() == 0         ? rsf
                        : rs.First() == 10        ? Trampoline(frame + 1, rsf + rs.Take(3).Sum(), rs.Skip(1))
                        : rs.Take(2).Sum() == 10  ? Trampoline(frame + 1, rsf + rs.Take(3).Sum(), rs.Skip(2))
                        :                           Trampoline(frame + 1, rsf + rs.Take(2).Sum(), rs.Skip(2));
                }
            }
            

            调用方法Game.RollMany 时有多次滚动:如果没有备用或罢工,通常为 20 次滚动。

            第一行立即调用蹦床函数:return Trampoline(1, 0, rs.ToList());。这个局部函数递归地遍历 rolls 数组。本地函数(蹦床)允许以两个附加值开始遍历:以frame 1 和rsf(到目前为止的结果)0 开始。

            在本地函数中有一个三元运算符,可以处理五种情况:

            • 游戏在第 11 帧结束:返回目前的结果
            • 如果没有更多的掷骰,游戏结束:返回目前的结果
            • Strike:计算帧得分并继续遍历
            • 备用:计算帧分数并继续遍历
            • 正常分数:计算帧分数并继续遍历

            通过再次调用蹦床来继续遍历,但现在使用更新的值。

            如需更多信息,请搜索:“tail recursion accumulator”。请记住,编译器不会优化尾递归。因此,尽管这种解决方案可能很优雅,但它可能不会是禁食的。

            【讨论】:

              【解决方案8】:
              typedef void* (*state_type)(void);
              void* state1();
              void* state2();
              void* state1() {
                return state2;
              }
              void* state2() {
                return state1;
              }
              // ...
              state_type state = state1;
              while (1) {
                state = state();
              }
              // ...
              

              【讨论】:

              • 您能否添加任何 cmets 或解释为什么这是蹦床?
              • Tthis 是一个函数指针,根据它调用的函数返回的内容从一个函数跳到下一个函数。
              猜你喜欢
              • 2012-03-09
              • 2021-07-16
              • 1970-01-01
              • 2011-12-27
              • 1970-01-01
              • 1970-01-01
              • 2014-12-30
              • 2012-12-06
              • 1970-01-01
              相关资源
              最近更新 更多