최근 직장에서 토론하는 동안 누군가는 트램폴린 기능을 언급했습니다.
Wikipedia 에서 설명을 읽었습니다 . 기능에 대한 일반적인 아이디어를 제공하는 것으로 충분하지만 좀 더 구체적인 것을 원합니다.
트램폴린을 설명하는 간단한 코드 스 니펫이 있습니까?
최근 직장에서 토론하는 동안 누군가는 트램폴린 기능을 언급했습니다.
Wikipedia 에서 설명을 읽었습니다 . 기능에 대한 일반적인 아이디어를 제공하는 것으로 충분하지만 좀 더 구체적인 것을 원합니다.
트램폴린을 설명하는 간단한 코드 스 니펫이 있습니까?
답변:
Wikipedia에 설명 된대로 '트램폴린'의 LISP 감각도 있습니다.
일부 LISP 구현에서 사용되는 트램폴린은 썽크 반환 함수를 반복적으로 호출하는 루프입니다. 하나의 트램폴린으로 프로그램의 모든 제어 이전을 표현하기에 충분합니다. 이렇게 표현 된 프로그램은 트램폴린 또는 "트램폴린 스타일"입니다. 프로그램을 trampolined 스타일로 변환하는 것은 trampolining입니다. Trampolined 함수는 스택 지향 언어에서 꼬리 재귀 함수 호출을 구현하는 데 사용할 수 있습니다.
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)
})
});
}
}
그러나 n = 25
Firefox 에서 이것을 실행하면 '너무 많은 재귀!'라는 오류가 발생합니다. 이제 이것은 트램폴린이 해결하는 문제 (자바 스크립트에서 꼬리 호출 최적화 누락)입니다. 함수를 (재귀 적) 호출하는 대신 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]});
}
다른 언어로 트램폴린으로 구현 된 팩토리얼 함수에 대한 몇 가지 예를 추가하겠습니다.
스칼라 :
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)))
}
자바:
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, ¶ms};
trampoline(&t);
printf("\n%d\n", params.product);
return 0;
}
if (n < 2) Done(product)
. 그래서 1 개의 기호를 편집 할 수 없었습니다 ...
온라인 게임의 치트 방지 패치에서 사용한 예를 들어 보겠습니다.
수정을 위해 게임에서로드하는 모든 파일을 스캔 할 수 있어야했습니다. 그래서이 작업을 수행하는 가장 강력한 방법은 CreateFileA에 트램폴린을 사용하는 것입니다. 따라서 게임이 시작될 때 GetProcAddress를 사용하여 CreateFileA의 주소를 찾은 다음 함수의 처음 몇 바이트를 수정하고 몇 가지 작업을 수행하는 내 "트램폴린"함수로 점프하는 어셈블리 코드를 삽입합니다. 그런 다음 jmp 코드 다음에 CreateFile의 다음 위치로 다시 이동합니다. 이를 안정적으로 수행하는 것은 그것보다 약간 까다 롭지 만 기본 개념은 한 함수를 연결하고 강제로 다른 함수로 리디렉션 한 다음 원래 함수로 다시 점프하는 것입니다.
편집 : 마이크로 소프트는 당신이 볼 수있는 이런 유형의 프레임 워크를 가지고 있습니다. 호출 본론
저는 현재 Scheme 인터프리터에 대한 꼬리 호출 최적화를 구현하는 방법을 실험하고 있으므로 현재 트램폴린이 나에게 적합한 지 알아 보려고 노력하고 있습니다.
내가 알기로는 기본적으로 트램폴린 함수에 의해 수행되는 일련의 함수 호출입니다. 각 함수를 썽크라고하며 프로그램이 종료 될 때까지 (빈 연속) 계산의 다음 단계를 반환합니다.
다음은 트램폴린에 대한 이해를 높이기 위해 작성한 첫 번째 코드입니다.
#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 !
다음은 중첩 함수의 예입니다.
#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
코드로 이동하여 해당 주소를 전달합니다.
ABI는 함수 포인터가 실제로 실행 코드에 대한 포인터와 데이터에 대한 또 다른 포인터를 모두 포함하는 구조 인 "팻 포인터"라고 지정하는 PowerPC와 같은 아키텍처에서는 이러한 혼란이 필요하지 않습니다. 그러나 x86에서 함수 포인터는 포인터 일뿐입니다.
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");
편집 : 더 많은 난해한 트램폴린이 컴파일러에 의해 암시 적으로 생성됩니다. 그러한 용도 중 하나는 점프 테이블입니다. (분명히 더 복잡한 코드가 있지만 복잡한 코드를 생성하려고할수록 더 복잡합니다.)
이제 C #에 로컬 함수 가 있으므로 볼링 게임 코딩 카타 는 트램폴린으로 우아하게 해결할 수 있습니다.
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 배열을 재귀 적으로 순회합니다. 로컬 함수 (트램폴린)를 사용하면 순회가 2 개의 추가 값으로 시작될 수 있습니다 : frame
1로 시작 및 rsf
(지금까지의 결과) 0으로 시작합니다.
로컬 함수에는 5 가지 경우를 처리하는 삼항 연산자가 있습니다.
순회를 계속하려면 트램폴린을 다시 호출하지만 이제는 업데이트 된 값을 사용합니다.
자세한 내용은 " tail recursion accumulator "를 검색하십시오 . 컴파일러는 꼬리 재귀를 최적화하지 않습니다. 따라서이 솔루션이 우아 할 수 있지만 금식은 아닐 것입니다.