由于有限递归而导致段错误

时间:2020-07-26 01:54:25

标签: c++ recursion valgrind

我收到一个段错误,我认为这是由大量递归调用引起的。已注意到here。我的困惑是递归不是无限的。一旦给定事件发生,就会有一个明确的断点,并且该事件最终将始终发生。我怀疑问题在于中断之前递归调用的数量太多了。 valgrind的输出:

==903368== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==903368==
==903368== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==903368==  Access not within mapped region at address 0x1FFE801FF8
==903368== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==903368==    at 0x4081B4: operator-(Point const&, Point const&) (Point.h:47)
==903368==  If you believe this happened as a result of a stack
==903368==  overflow in your program's main thread (unlikely but
==903368==  possible), you can try to increase the size of the
==903368==  main thread stack using the --main-stacksize= flag.
==903368==  The main thread stack size used in this run was 8388608.
==903368== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==903368==
==903368== Process terminating with default action of signal 11 (SIGSEGV)
==903368==  Access not within mapped region at address 0x1FFE801FE8
==903368== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==903368==    at 0x482F14D: _vgnU_freeres (vg_preloaded.c:57)
==903368==  If you believe this happened as a result of a stack
==903368==  overflow in your program's main thread (unlikely but
==903368==  possible), you can try to increase the size of the
==903368==  main thread stack using the --main-stacksize= flag.
==903368==  The main thread stack size used in this run was 8388608.
==903368==
==903368== HEAP SUMMARY:
==903368==     in use at exit: 86,717 bytes in 40 blocks
==903368==   total heap usage: 52,536 allocs, 52,496 frees, 47,716,014 bytes allocated
==903368==
==903368== LEAK SUMMARY:
==903368==    definitely lost: 0 bytes in 0 blocks
==903368==    indirectly lost: 0 bytes in 0 blocks
==903368==      possibly lost: 0 bytes in 0 blocks
==903368==    still reachable: 86,717 bytes in 40 blocks
==903368==         suppressed: 0 bytes in 0 blocks
==903368== Rerun with --leak-check=full to see details of leaked memory
==903368==
==903368== For lists of detected and suppressed errors, rerun with: -s
==903368== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)

试图复制:

void f(int i, const int& niter) {
    i++;
    if (i < niter)
        f(i, niter);
    else 
        return;
}

int main()
{
    // fine
    f(0, 1000);
    
    // not fine
    f(0, 1000000);
    return 0;
}

我应该按照valgrind的建议增加主堆栈的大小吗?还是完全避免递归?

int main()
{
    int i = 0;
    while (i < 1000000) 
        i++;
}

3 个答案:

答案 0 :(得分:3)

每个递归级别占用堆栈上的一定数量的内存。在所有平台上,我都知道堆栈的大小固定(但可配置)相对较小。

最终,递归的数量限制为(stack size) / (stack used per call),因此,要增加递归的数量,您需要增大堆栈的大小或减小每次调用所需的大小。在您的示例中,您似乎有一个8mb的堆栈,因此要实现1,000,000个递归级别,每个级别只能在堆栈上分配8个字节,这在大多数实现中可能是不可能的。

减小每次调用大小的一种方法是将引用传递给包含任何静态参数而不是单个参数的结构。在您的示例情况下,这将无济于事,因为您只有一个静态参数。在int小于指针的平台上(例如,大多数64位平台),可能会有用的是删除const&,因为引用很可能作为指针传递。

如果上述方法不起作用,则应使用非递归实现替换递归,并将您的状态存储在std::stack中,该状态是堆分配的,因此不会导致堆栈溢出。

答案 1 :(得分:3)

您应该将函数更改为非递归。在大多数现代体系结构(包括x86)中,递归是在堆栈上实现的,并且堆栈受到限制:它相对较小,并且不会根据需要增长。因此,应避免作为经验法则的递归函数,因为它们具有使堆栈饿死的已知问题。

递归似乎是解决问题的最佳选择,但这只是您作为人类对算法的思考而采用的方式。但是,每个递归函数都可以相对容易地转换为以std::stack之类的动态结构保持步态的非递归函数。另外,许多递归算法至少都有一个已知的非递归等效算法。

因此,我鼓励您将函数转换为非递归函数,并完全避免递归函数(在学习练习之外)

答案 2 :(得分:0)

正如其他人提到的那样,递归很昂贵,应该采用迭代的方式。但是,如果您对数学有一点了解,则可以使您的问题非常有效....

简单地说,您可以将变量的最后一个值存储在这样的变量中:

int last = 1000;

,然后执行以下操作:

int total = (last + 1)*last /2;

所以这使您的算法为O(1);