什么是蹦床功能?


93

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

我已经在Wikipedia上阅读了说明。给出功能的一般概念就足够了,但是我想更具体一些。

您是否有一个简单的代码片段来说明蹦床?


2
在微软世界中,蹦床通常被称为“暴徒”。[以下为页面] [1],来自Andrei Alexandrescu的“现代C ++设计” ---- [1]:books.google.com/…–
迈克尔·伯尔


它基本上是可以使用setjmp / lomgjmp实现的某些功能的通用形式,即避免堆栈ovwerflow。
Ingo 2013年

12
为什么有人要避免stackoverflow?
Nikole,2016年

Answers:


72

如Wikipedia所述,还有LISP对“蹦床”的感觉:

在一些LISP实现中使用的蹦床是一个循环调用循环返回功能的循环。一个蹦床就足以表达一个程序的所有控制权。如此表达的节目被践踏或以“踩踏风格”进行;将程序转换为流浪风格是流浪汉。Trampolined函数可用于以面向堆栈的语言实现尾递归函数调用

假设我们正在使用Javascript,并希望以延续传递样式编写朴素的Fibonacci函数。我们这样做的原因无关紧要-例如将Scheme移植到JS,或者使用CPS,无论如何我们都必须使用CPS来调用服务器端函数。

所以,第一次尝试是

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

但是,n = 25在Firefox中与一起运行会产生错误“太多的递归!”。现在,这正是蹦床解决的问题(在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]});
}

39

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

Scala:

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例子,以及以下关于嵌套函数的短暂答案,最终使我理解了蹦床。一种辅助功能,可以像关闭一样用于更新状态。
字节

Scala代码应更正if (n < 2) Done(product),所以没有允许我编辑1符号...
马克斯

21

我将举一个我在在线游戏的反作弊补丁中使用的示例。

我需要能够扫描游戏正在加载的所有文件进行修改。因此,我发现执行此操作的最可靠的方法是对CreateFileA使用蹦床。因此,当游戏启动时,我将使用GetProcAddress找到CreateFileA的地址,然后修改该函数的前几个字节,并插入将跳转到我自己的“蹦床”函数的汇编代码,在其中执行一些操作,以及然后在我的jmp代码之后,我将跳回到CreateFile中的下一个位置。能够可靠地执行此操作比这要难一些,但是基本概念只是钩住一个功能,强制其重定向到另一个功能,然后跳回到原始功能。

编辑:微软为您提供了此类框架。叫绕路


8

我目前正在尝试对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 !

7

这是嵌套函数的示例:

#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上,函数指针只是一个指针。


0

对于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");

编辑:更多深奥的蹦床将由编译器隐式生成。这样的用途之一就是跳转表。(尽管显然,越复杂的代码越难尝试生成复杂的代码。)


0

既然C#具有本地功能,那么就可以用蹦床优雅地解决保龄球游戏代码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数组。局部函数(蹦床)允许遍历以两个附加值开始:以frame1 开始和rsf(到目前为止的结果)0。

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

  • 游戏在第11帧结束:到目前为止返回结果
  • 如果没有更多的掷骰,则游戏结束:到目前为止返回结果
  • 打击:计算框架得分并继续遍历
  • 备用:计算框架分数并继续遍历
  • 普通分数:计算框架分数并继续遍历

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

有关更多信息,请搜索:“ 尾递归累加器 ”。请记住,编译器不会优化尾递归。因此,尽管此解决方案可能很优雅,但它可能不会被禁食。


-2
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();
}
// ...

3
您可以添加任何关于为什么这是蹦床的评论或解释吗?
prasun 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.