【问题标题】:Intended infinite recursion, no-return functions预期的无限递归,无返回函数
【发布时间】:2011-10-19 10:53:40
【问题描述】:

通常不需要无限递归,当它发生时通常会导致堆栈溢出或段错误。

但出于理论考虑,出于好奇,我一直在考虑是否可以有意地创建实际的无限递归。

在 C++ 和 C 中工作,其中堆栈通常会随着每个函数调用而增长,并且每个函数都会返回并弹出它处理的堆栈部分。

这就是想法。是否可以强制一个函数清除它自己的堆栈空间,然后调用另一个函数,以便新函数有效地替换第一个函数,而不需要第一个函数返回然后通过循环再次触发。

如果有的话,我不仅在考虑将普通循环作为可能的用途。循环通常在他们所做的事情上做得很好。但是,如果您要使用它通过节点网络发送信号,这些信号会在它们自己的进程线程中无限期地进行,直到达到特定条件。它可能是一个可以用来解决一些问题的工具。

请记住,我并不是真的在问它是否实用,只是在可能的情况下。为了科学!

【问题讨论】:

  • 你的第四段没有描述递归。
  • 我描述了一种可能的用途,我正在单独询问递归。你的说法有什么意义?
  • 您可能想了解en.wikipedia.org/wiki/Continuation-passing_style(它的使用依赖于适当的尾调用优化)。
  • @Patrick87:我不确定我是否看到了您的示例,现在找不到了。你删除了吗?
  • @Zoomulator:是的,原来我犯了一个错误。对不起...请忽略。

标签: c++ c theory


【解决方案1】:

是否可以强制一个函数清除它自己的堆栈 空间然后调用另一个函数,以便新函数 有效地替换了第一个函数

这用于tail-call-optimization,所以是的,它是可能的并且在实践中使用。在像 C++ 这样的语言中,这是一个很好的特性,因为有时使用递归来表达算法更简单,但使用循环更有效地实现。在某些情况下,转换可以由编译器自动完成。

【讨论】:

  • 这不是 OP 所要求的:尾调用优化是用循环替换递归,而他说的是清除为函数调用分配的帧堆栈。
  • @akappa:不,尾调用优化正是 OP 所描述的。如果它是对当前函数的递归调用,那么你最终会得到一个你描述的循环;可以通过擦除当前堆栈帧并分支到另一个函数来实现对另一个函数的尾调用。
  • @akappa:你确定尾调用优化总是产生循环吗?如果 a 函数根据条件调用不同的其他函数怎么办?这不容易被循环替换,但可以优化掉。
  • @akappa,您如何用循环替换间接尾调用,例如return (*someptr)(args...)
  • 嗯,我在快速排序的上下文中看到了尾调用优化,它是在源代码中手动完成的优化。看来您是对的,而我对“尾递归”的想法是错误的。
【解决方案2】:

有一种称为蹦床的技术用于在不使用尾调用优化的情况下实现continuation-passing style 编程。如果您考虑任何不支持 TCO 的语言,例如 JavaScript,以及使用该语言研究 CPS 解决方案,那么该解决方案很可能涉及蹦床。

本质上,蹦床有一个称为蹦床的函数,它迭代地调用thunk-returning函数。

我知道你说过“第一个函数不需要返回然后通过循环再次触发”——这就是蹦床——但考虑到这是 C++,例如,通过返回离开范围是核心的核心通过析构函数设计 C++ 的自动资源管理(参见:RAII)。您也可以使用 C 的 setjmp()/longjmp() 函数来清除堆栈,但是您需要非常小心以确保正确释放所有资源。

【讨论】:

  • 您需要一个内联程序集才能在 C++ 中高效地进行蹦床。然而,它的效率明显低于简单的倒带堆栈和跳转序列。
【解决方案3】:

这确实让我想起了可以在汇编代码中完成的优化。假设你有这个:

  call FuncA
  call FuncB

您可以将其替换为:

  push ReturnAddress
  push FuncB
  jmp FuncA
ReturnAddress:

这会导致FuncA 末尾的ret 直接跳转到FuncB,而不是返回到调用者然后跳转到FuncB。在高级语言中确实不可能。

还有这个:

 call FuncA
 ret

可以改成:

 jmp FuncA

【讨论】:

  • 我一直在使用你的第二个技巧。但是你的第一个构造有什么意义呢?
  • @TonyK:第一个将四个跳转 (call-ret-call-ret) 替换为三个 (jmp-ret-ret)。而且它不限于两个呼叫,您可以菊花链任意数量的呼叫。
  • 实际上它用 push-push-jmp-ret-ret 代替了 call-ret-call-ret。这有什么改善吗?
  • 好吧,call-ret-call-ret 是一个 push-jump-ret-push-jump-ret,所以最好加上你只为代码做一个内存加载,即代码在呼叫之间不会重新加载呼叫 - 加载三个代码而不是四个。内存访问越少越好。
  • 我对如何实现汇编代码相当陌生。是否有一种合理的方法可以将其作为 C/C++ 中的内联包装器来使函数以这种方式运行?不过还是谢谢你的回答!
猜你喜欢
  • 2013-02-24
  • 1970-01-01
  • 2020-07-10
  • 1970-01-01
  • 1970-01-01
  • 2018-12-05
  • 2020-08-24
  • 2016-01-22
  • 1970-01-01
相关资源
最近更新 更多