编程语言是无堆栈的到底意味着什么?

发布于 2024-07-18 00:58:14 字数 476 浏览 7 评论 0原文

根据这个答案

https://stackoverflow.com/questions/551950/ What-stackless-programming-languages-are-available/671296#671296

所有这些编程语言都是无堆栈的

  • Stackless Python
  • PyPy
  • Lisp
  • Scheme
  • Tcl
  • Lua
  • Parrot VM

对它们来说无堆栈到底意味着什么? 这是否意味着他们不使用调用堆栈? 如果他们不使用调用堆栈,那么他们使用什么?

According to this answer

https://stackoverflow.com/questions/551950/what-stackless-programming-languages-are-available/671296#671296

all of these programming languages are stackless

  • Stackless Python
  • PyPy
  • Lisp
  • Scheme
  • Tcl
  • Lua
  • Parrot VM

What does it really mean for them to be stackless? Does it mean they don't use a call stack? If they don't use a call stack, what do they use?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

你另情深 2024-07-25 00:58:14

无堆栈对他们来说到底意味着什么? 这是否意味着他们不使用调用堆栈?

是的,差不多是这样。

如果他们不使用调用堆栈,他们会使用什么?

当然,具体的实现因语言而异。 在 Stackless Python 中,有一个调度程序,它使用最顶层的帧及其结果来启动 Python 解释器。 解释器根据需要一次处理一个操作码,直到到达 CALL_FUNCTION 操作码,即您将要进入函数的信号。 这会导致调度程序使用相关信息构建一个新帧,并使用展开标志返回给调度程序。 从那里,调度程序重新开始,将解释器指向最上面的框架。

无堆栈语言出于多种原因避开调用堆栈,但在许多情况下,使用它是为了使某些编程结构变得更容易实现。 规范的一个是延续。 延续是非常强大、非常简单的控制结构,可以表示您可能已经熟悉的任何常用控制结构(whiledoif、switch 等)。

如果这令人困惑,您可能想尝试一下维基百科文章,特别是可爱的延续三明治类比

假设您在厨房的冰箱前,正在考虑吃三明治。 你就在那里拿一个延续并将其放在口袋里。 然后你从冰箱里拿出一些火鸡和面包,给自己做一个三明治,现在它就放在柜台上。 您调用口袋中的延续,然后您发现自己再次站在冰箱前,想着三明治。 但幸运的是,柜台上有一个三明治,而且制作它的所有材料都没有了。 那你就吃吧。

What does it really mean for them to be stackless? Does it mean they don't use a call stack?

Yes, that's about right.

If they don't use a call stack, what do they use?

The exact implementation will, of course, vary from language to language. In Stackless Python, there's a dispatcher which starts the Python interpreter using the topmost frame and its results. The interpreter processes opcodes as needed one at a time until it reaches a CALL_FUNCTION opcode, the signal that you're about to enter into a function. This causes the dispatcher to build a new frame with the relevant information and return to the dispatcher with the unwind flag. From there, the dispatcher begins anew, pointing the interpreter at the topmost frame.

Stackless languages eschew call stacks for a number of reasons, but in many cases it's used so that certain programming constructs become much easier to implement. The canonical one is continuations. Continuations are very powerful, very simple control structures that can represent any of the usual control structures you're probably already familiar with (while, do, if, switch, et cetera).

If that's confusing, you may want to try wrapping your head around the Wikipedia article, and in particular the cutesy continuation sandwich analogy:

Say you're in the kitchen in front of the refrigerator, thinking about a sandwich. You take a continuation right there and stick it in your pocket. Then you get some turkey and bread out of the refrigerator and make yourself a sandwich, which is now sitting on the counter. You invoke the continuation in your pocket, and you find yourself standing in front of the refrigerator again, thinking about a sandwich. But fortunately, there's a sandwich on the counter, and all the materials used to make it are gone. So you eat it.

失而复得 2024-07-25 00:58:14

它们不使用调用堆栈,因为它们以延续传递风格运行。 如果您不熟悉尾部调用优化,这可能是理解其含义的良好第一步。

为了模拟此模型上的传统调用/返回,调用者不会推送返回地址并期望帧的其余部分保持不变,而是关闭其代码的其余部分以及仍然需要的任何变量(其余部分被释放)。 然后,它对被调用者执行尾调用,将此延续作为参数传递。 当被调用者“返回”时,它通过调用此延续并将返回值作为参数传递给它来实现。

就上述而言,这只是一种执行函数调用的复杂方法。 然而,它可以很好地推广到更复杂的场景:

  1. 异常/最后/等块非常容易建模 - 如果您可以传递一个“返回”延续作为参数,那么您可以同样轻松地传递 2 个(或更多)。 lisp-y“条件处理程序”块(可能会也可能不会将控制权返回给调用者)也很容易 - 为该函数的其余部分传递一个延续,该函数可能会被调用,也可能不会被调用。
  2. 多个返回值同样变得容易 - 将多个参数传递给延续。
  3. 返回临时值/复制不再与函数参数传递不同。 这通常可以更轻松地消除临时对象。
  4. 尾递归优化是微不足道的 - 调用者只需传递它收到的“返回”延续,而不是捕获新的延续。

They don't use a call stack, because they operate in continuation-passing style. If you're not familiar with tail call optimization, that's probably a good first step to understanding what this means.

To emulate the traditional call/return on this model, instead of pushing a return address and expecting the remainder of the frame to remain untouched the caller closes over the remainder of its code and any variables that are still needed (the rest are freed). It then performs a tail call to the callee, passing this continuation as an argument. When the callee "returns", it does so by calling this continuation, passing the return value as an argument to it.

As far as the above goes, it's merely a complicated way to do function calls. However, it generalizes very nicely to more complex scenarios:

  1. exception/finally/etc blocks are very easily modeled - if you can pass one "return" continuation as an argument, you can pass 2 (or more) just as easily. lisp-y "condition handler" blocks (which may or may not return control to the caller) are also easy - pass a continuation for the remainder of this function, that might or might not be called.
  2. Multiple return values are similarly made easy - pass several arguments on to the continuation.
  3. Return temporaries/copying are no longer different than function argument passing. This often makes it easier to eliminate temporaries.
  4. Tail recursion optimization is trivial - the caller simply passes on the "return" continuation it received rather than capturing a new one.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文