【问题标题】:Why is the recursion depth non-deterministic (C++)?为什么递归深度是不确定的(C++)?
【发布时间】:2021-03-23 23:55:03
【问题描述】:

重复运行以下 C++ 程序会在出现分段错误之前给出不同的最大递归调用数(相差大约 100 个函数调用)。

#include <iostream>

void recursion(int i)
{
    std::cout << "iteration: " << ++i  << std::endl;
    recursion(i);
}

int main()
{
    recursion(0);
};

我用

编译了文件main.cpp
g++ -O0 main.cpp -o main

Herehere 针对 java 讨论了与上述相同的问题。在这两种情况下,答案都是基于 java 相关概念、JIT、垃圾回收、HotSpot 优化器等。

为什么 C++ 的最大递归数会有所不同?

【问题讨论】:

  • 未定义的行为是未定义的。在不同的运行中没有什么异常会产生不同的结果。没有人答应过你。
  • 我不知道 infinite recursions 是 C++ 中未定义的行为,这解释了问题的 C++ 方面。但是在终止/终止进程之前操作系统“允许”的操作数量存在一些差异 - 至少在堆栈已满时?
  • ASLR 可能对此负责,请尝试禁用它,看看会发生什么。
  • 禁用 ASLR 会导致递归次数恒定。这回答了问题的操作系统方面。谢谢!我在下面记录了我所做的事情。

标签: c++ function recursion stack


【解决方案1】:

除了 C++ 方面:跟随 Eljay 和 n.'pronouns'.m 的 cmets,我转向了 ASLR。 This 帖子描述了如何做到这一点。简而言之,可以通过

禁用ASLR
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

并通过

启用
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space

禁用 ASLR 后,系统分段错误之前的递归次数对于重复执行所描述的程序是恒定的。

【讨论】:

    【解决方案2】:

    当您炸毁堆栈时会发生什么并不一定会崩溃。根据系统的不同,您可能只是在相对随机的内存空间中浪费内存。

    该内存中的内容可能取决于发生的内存分配、操作系统在您请求时分配给您的连续内存量、ASLR 等。

    C++ 中未定义的行为是不可预测的。

    【讨论】:

    • 我不知道无限循环是 C++ 中未定义的行为。相关N1528
    • @dgru 这不是无限循环。它是无限递归。 C++ 实现可以限制递归,之后行为是未定义的(也就是炸栈)
    • 会导致“在你的内存空间的随机位中丢弃”的过程是什么?操作系统不应该检查吗?
    • @dgruending 进程的内存空间。许多操作系统(试图)保护系统免受进程的影响,但很少保护进程免受自身的影响。进程崩溃是操作系统保护系统免受进程影响的一个例子。一个进程在其自己的堆上写入并导致新/删除操作显着失败是进程损坏自身的一个例子。防御如何运作取决于系统;它是在堆栈中放置大量内存保护页,还是堆栈直接溢出到堆中?
    【解决方案3】:

    您的递归永远不会在逻辑上终止。它仅在您的程序因堆栈空间不足而崩溃时终止。

    每次递归调用都会使用一定数量的堆栈空间,但在 C++ 中,并没有准确定义有多少堆栈空间可用以及每次递归调用使用多少。

    每次调用使用的堆栈空间可能因优化设置、链接器选项、对齐要求、程序的启动方式以及大量其他因素而异。

    底线:您编写了一个错误,并且您在编译器和平台中运行了未定义的行为。如果您想准确计算程序在其当前线程上具有多少堆栈空间,您的平台将拥有您可以调用的 API 来获取该值。

    【讨论】:

    • 但我编译过一次。每次可用的堆栈空间也是相同的。对于重复运行相同的二进制文件,执行不应该独立于优化和链接器设置吗?也许这不是一个 C++ 问题,而是与操作系统如何终止程序有关?
    • @dgruending • 您的平台是否支持ASLR
    • @Eljay 我正在运行 Ubuntu 20.04,所以我认为您的问题的答案是“是”。
    • “它只会在你的程序因堆栈空间不足而崩溃时终止。”。也可以通过int溢出到达UB(而且不只是理论上,尾递归可以在这里轻松优化)。
    猜你喜欢
    • 2015-01-18
    • 1970-01-01
    • 2015-01-08
    • 2011-02-07
    • 2014-01-09
    • 1970-01-01
    • 1970-01-01
    • 2015-11-22
    • 2011-05-29
    相关资源
    最近更新 更多