我听说过无栈语言,但我不知道这样的语言如何实现。有人可以解释一下吗?
我听说过无栈语言,但我不知道这样的语言如何实现。有人可以解释一下吗?
使用堆分配栈帧有几个原因:
如果程序在解决特定问题时进行深度递归,很难预先分配“大堆栈”区域,因为所需大小未知。人们可以笨拙地安排函数调用来检查是否还有足够的堆栈空间,如果没有,就重新分配一个更大的块,复制旧堆栈并调整指向堆栈的所有指针;这样的操作非常笨拙,我不知道是否有任何实现。分配堆栈帧意味着应用程序直到没有可分配的内存为止都不必说抱歉。
程序分叉子任务。每个子任务都需要自己的堆栈,因此不能使用提供的一个“大堆栈”。因此,需要为每个子任务分配堆栈。如果有数千个可能的子任务,你可能现在需要数千个“大堆栈”,而内存需求突然变得荒谬起来。分配堆栈帧解决了这个问题。通常子任务的“堆栈”会引用父任务来实现词法作用域;当子任务分叉时,创建了一个称为“仙人掌堆栈”的“子堆栈”树。
你的语言有continuations。这要求当前函数中词法作用域中可见的数据在以后可以被保留以供重复使用。这可以通过复制父堆栈帧,向上爬升仙人掌堆栈并继续进行来实现。
我实现的PARLANSE编程语言可以完成1)和2)。目前正在开发3)。有趣的是,PARLANSE会从一个非常快速访问的线程专用堆栈中分配堆栈帧;这通常只需要4个机器指令。当前实现基于x86架构,分配的帧与其他传统的x86编程语言实现类似,被放置在x86 EBP/ESP寄存器中。因此,它确实使用硬件的“连续堆栈”(包括推入和弹出),只是以块的形式进行。对于大量已知堆栈需求的实用程序代码,它还会生成“框架本地”子例程调用,不会切换堆栈。
Stackless Python仍然有一个Python栈(虽然它可能具有尾递归优化和其他调用帧合并技巧),但它与解释器的C栈完全分离。
Haskell(通常实现)没有调用栈;计算是基于图形缩减进行的。
有一篇关于语言框架Parrot的好文章。 Parrot不使用堆栈进行调用,本文解释了这种技术。
假设你想要实现无栈C。首先需要意识到的是这不需要一个堆栈:
a == b
但是,这样做吗?
isequal(a, b) { return a == b; }
不需要。因为聪明的编译器会将对isequal
的调用内联,将其转换为a == b
。那么,为什么不将所有内容都内联呢?当然,这将生成更多的代码,但如果你认为摆脱堆栈是值得的,那么这个小妥协很容易实现。
递归怎么办?没问题。像这样的尾递归函数:
bang(x) { return x == 1 ? 1 : x * bang(x-1); }
仍然可以内联,因为它实际上只是一个伪装成for循环的函数:
bang(x) {
for(int i = x; i >=1; i--) x *= x-1;
return x;
}
ax = x;
NOTDONE:
if(ax > 1) {
x = x*(--ax);
goto NOTDONE;
}
有一种情况下,您需要做出一些小的妥协。这个无法内联:
fib(n) { return n <= 2 ? n : fib(n-1) + fib(n-2); }
Stackless C无法做到这一点。你会失去很多吗?其实不会。普通的C语言也做不好这件事。如果你不相信,只需调用fib(1000)
,看看你珍贵的计算机会发生什么。
如果我错了,请随意纠正,但我认为为每个函数调用帧在堆上分配内存会导致极度的内存抖动。毕竟操作系统必须管理这些内存。我认为避免这种内存抖动的方法是使用调用帧缓存。因此,如果您需要缓存,我们可以将其连续地放置在内存中并称之为堆栈。