【问题标题】:Does C++ limit recursion depth?C++ 是否限制递归深度?
【发布时间】:2011-02-07 11:21:23
【问题描述】:

在 Python 中有一个最大递归深度。似乎是因为 Python 是解释而不是编译的。 C++ 有相同的概念吗?还是只连接内存限制?

【问题讨论】:

  • 我假设您的意思是 最大 递归深度。 (在遇到错误之前允许递归的深度)
  • 我不明白为什么作为解释器/编译器与此有关。例如,Stackless Python 仍然是一个解释器,但它的堆栈帧不使用 C 堆栈,因此不存在同样的问题,不是吗?
  • 请注意,有 C++ 和 Python 实现可以消除尾调用以避免在这些情况下达到堆栈限制。
  • @Ken stackless 如果无法消除递归调用,最终仍会耗尽资源,例如尾调用优化

标签: c++ recursion limit


【解决方案1】:

C++ 中的限制是由于堆栈的最大大小。这通常比 RAM 的大小小几个数量级,但仍然很大。 (幸运的是,像字符串 contents 这样大的东西通常不保存在堆栈本身上。)

堆栈限制通常在操作系统级别可调。 (如果您在 Unix 上,请参阅内置 ulimit shell 的文档。)这台机器 (OSX) 上的默认值为 8 MB。

[编辑] 当然,在计算递归深度时,堆栈的大小本身并不完全有帮助。要知道这一点,您必须计算递归函数(也称为堆栈帧)的激活记录(或记录)的大小。最简单的方法(据我所知)是使用反汇编程序(大多数调试器的功能)并在每个函数的开头和结尾读出堆栈指针调整的大小。这很乱。 (您可以通过其他方式进行计算——例如,计算两次调用中指向变量的指针之间的差异——但它们甚至更糟糕,尤其是对于可移植代码。从反汇编中读取值更容易 IMO。)

【讨论】:

  • 我知道我应该为这些单位说 MiB,但这对我来说完全是另外一回事。所以我会选择 67.1 兆比特!
  • 在 Windows 机器上,您可以 A. 安全地处理堆栈溢出条件,并且 B. 为每个可执行文件单独设置堆栈深度(这是一个链接器选项)。由于这些特性,Unix 机器通常使用比 Windows (1MB) 更深的堆栈 (8MB)。 +1
  • 我认为这两个操作系统家族在这方面处理虚拟内存的方式也存在差异。细节开始变得相当重要。
  • 仅供读者参考:您可能已经在某些圈子中看到了称为“堆栈帧”的“激活记录”:)
  • 这里在 linux (x86_64) 上,help ulimit 表示可以通过运行 ulimit -s 来检索堆栈大小,这会产生 8192,相对于在没有 unlimited 的情况下运行它会产生 @987654326 @ 转变。如果没有提供,则使用的默认限制是 -f: the maximum size of files written by the shell and its children。 YMMV 在其他 Unix 上 [Unices?],但我认为值得指出。
【解决方案2】:

不,C++ 没有明确的递归深度。如果超过最大堆栈大小(Windows 默认为 1 MB),您的 C++ 程序将溢出堆栈并终止执行。

【讨论】:

  • 是的。 +1。此外,重要的是要注意终止是即时的——类似于调用 TerminateProcess。您的进程的关闭功能(即 DLL_PROCESS_DETACH、DLL_THREAD_DETACH 等)都不会被调用。
  • 绝对 - 这是终止程序的最恶劣方式之一。
【解决方案3】:

C 或 C++ 标准中没有递归深度跟踪或限制。在运行时,深度受限于堆栈可以增长的大小。

【讨论】:

  • 实际上,限制是平台的寻址能力、进程可用的内存量以及编译器设置的任何限制的组合。通常,应用程序可以覆盖堆栈并执行未定义的行为。
  • 此外,任何ulimit 样式的运行时限制,或者在线程的情况下,在线程创建期间设置的任何限制。我不确定“覆盖堆栈”是指“将其设置为操作系统无法支持的大小(例如无限制),因为将首先达到其他限制”,还是“在堆栈上推太多,并且超越终点。”没有什么比在您的递归函数中拥有 256K 的自动存储、最后超出数量不足的“只读”堆栈保护页面、以及获得分段错误或堆损坏而不是写入只读页面错误。 ..
【解决方案4】:

Python 在递归调用上有一个tunable limit,而 C++ 则受到堆栈大小的限制。

此外,许多语言或编译器可以通过删除调用者的堆栈帧来优化尾递归,这样就不会消耗额外的堆栈空间。 (在尾递归中,调用函数唯一要做的就是在递归调用之后返回递归调用的返回值。)

int fact(int n, int accum=1){
  if (n==0) return accum;
  else return fact(n-1,n*accum); //tail recursion here.
}

Python 不优化尾递归(但stackless Python 会),C++ 不需要尾递归优化,但我相信 gcc 优化了尾递归。 JVM 不会优化尾递归,尽管 Scala 语言在某些常见的文档案例中会这样做。 Scheme 和 Lisp(可能还有其他函数式语言)要求优化尾递归。

【讨论】:

    【解决方案5】:

    C++ 确实有一个最大递归深度,受堆栈限制。但是,现代操作系统能够在用户空间堆栈填满时动态扩展它,从而仅通过内存空间和内存碎片限制递归深度。

    【讨论】:

    • 嗯.. 我并不完全肯定 Unixen 提供了该功能。但我可能是非常非常错误的 :) 似乎 boost::regex 会使用这些功能,如果它们可用的话......它会在 Windows 上使用它们。
    • 动态堆栈扩展听起来很有趣。你有参考吗?
    • setrlimit(2) 描述了一个 RLIMIT_STACK 资源,并且手册页没有提到它是特定于 Linux 的。 linux.die.net/man/2/setrlimit
    • 啊——我明白为什么boost::regex 不这样做了。您需要设置一个SIGSEGV 处理程序并在备用堆栈上捕获所有此类异常来处理它。并希望某处没有合法的访问违规。
    • 我过去读到的关于堆栈扩展的唯一内容是关于分页。基本上没有为堆栈分配页面(4Kb),而是在堆栈指针想要访问保护页面时动态分配。 (保护页是最后分配的页之后的页)。但它确实从 1MB 的最大限制中释放了一个。限制不是您的 RAM 大小。
    【解决方案6】:

    我相信限制是平台上可用堆栈的大小。根据我的阅读,Linux 上默认为 8K 8MB,但现代内核可以动态调整堆栈大小。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-01
      • 2012-10-26
      • 2016-03-02
      • 2020-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-29
      相关资源
      最近更新 更多