如何处理或避免C ++中的堆栈溢出

时间:2012-08-27 16:57:03

标签: c++ error-handling crash stack-overflow

在C ++中,堆栈溢出通常会导致程序无法恢复。对于需要非常强大的程序,这是一种不可接受的行为,特别是因为堆栈大小有限。关于如何处理问题的一些问题。

  1. 有没有办法通过一般技术来防止堆栈溢出。 (一种可扩展,强大的解决方案,包括处理大量堆栈的外部库等)。

  2. 有没有办法处理堆栈溢出,以防它们发生?优选地,堆栈被解开,直到有处理器来处理该类问题。

  3. 有些语言,有可扩展堆栈的线程。在C ++中是这样的吗?

  4. 对于解决C ++行为的任何其他有用的评论将不胜感激。

6 个答案:

答案 0 :(得分:23)

处理堆栈溢出不是正确的解决方案,相反,您必须确保程序不会溢出堆栈。

不要在堆栈上分配大变量(“大”取决于程序)。确保任何递归算法在已知最大深度后终止。如果递归算法可能递归未知次数或大量次数,则自行管理递归(通过维护自己动态分配的堆栈)或将递归算法转换为等效迭代算法

必须“非常强大”的程序不会使用“吃掉大量堆栈”的第三方或外部库。


请注意,某些平台会在发生堆栈溢出时通知程序,并允许程序处理错误。例如,在Windows上,抛出异常。但是,此异常不是C ++异常,它是异步异常。虽然只能通过throw语句抛出C ++异常,但在程序执行期间可能会随时抛出异步异常。但是,这是预期的,因为堆栈溢出可能随时发生:任何函数调用或堆栈分配都可能溢出堆栈。

问题是堆栈溢出可能导致异步异常被抛出,即使是不希望抛出任何异常的代码(例如,来自C ++中标记为noexceptthrow()的函数)。因此,即使您以某种方式处理此异常,也无法知道您的程序处于安全状态。因此,处理异步异常的最佳方法是不在所有(*)处理它。如果抛出一个,则表示程序包含错误。

其他平台可能有类似的方法来“处理”堆栈溢出错误,但任何此类方法都可能遇到同样的问题:预计不会导致错误的代码可能会导致错误。

(*)有一些非常罕见的例外情况。

答案 1 :(得分:7)

您可以使用良好的编程实践来防止堆栈溢出,例如:

  1. 对递归非常小心,我最近看到由于编写错误的递归CreateDirectory函数导致的SO,如果您不确定您的代码是否100%正确,则添加将在N次递归调用后停止执行的保护变量。或者更好的是不要写递归函数。
  2. 不要在堆栈上创建大型数组,这可能是隐藏数组,就像一个非常大的数组作为类字段。使用矢量总是更好。
  3. 要非常小心alloca,特别是如果它被放入某个宏定义中。我已经看到由字符串转换宏产生的大量SO,这些宏用于使用alloca进行快速内存分配的for循环。
  4. 确保您的堆栈大小是最佳的,这在嵌入式平台中更为重要。如果你的线程没有做太多,那么给它小堆栈,否则使用更大。我知道预约应该只占用一些地址范围 - 而不是物理内存。
  5. 这些是我在过去几年中看到的最多的SO原因。

    对于自动搜索结果,您应该能够找到一些静态代码分析工具。

答案 2 :(得分:2)

C ++是一种强大的语言,凭借这种力量可以射击自己。当发生堆栈溢出时,我不知道有任何可移植的机制来检测和纠正/中止。当然,任何此类检测都将是特定于实现的。例如,g ++提供-fstack-protector来帮助监控您的堆栈使用情况。

一般来说,最好的办法是主动避免使用基于堆栈的大型变量,并小心使用递归调用。

答案 3 :(得分:2)

Re:可扩展堆栈。你可以用这样的东西给自己更多的堆栈空间:

#include <iostream>

int main()
{
    int sp=0;

    // you probably want this a lot larger
    int *mystack = new int[64*1024];
    int *top = (mystack + 64*1024);

    // Save SP and set SP to our newly created
    // stack frame
    __asm__ ( 
        "mov %%esp,%%eax; mov %%ebx,%%esp":
        "=a"(sp)
        :"b"(top)
        :
        );
    std::cout << "sp=" << sp << std::endl;

    // call bad code here

    // restore old SP so we can return to OS
    __asm__(
        "mov %%eax,%%esp":
        :
        "a"(sp)
        :);

    std::cout << "Done." << std::endl;

    delete [] mystack;
    return 0;
}

这是gcc的汇编语法。

答案 4 :(得分:0)

我认为那行不通。推送/弹出 esp 比移动到寄存器要好,因为你不知道编译器是否会决定使用 eax 来做某事。

答案 5 :(得分:-1)

给你: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/resetstkoflw?view=msvc-160

  1. 您不会自己捕获 EXCEPTION_STACK_OVERFLOW 结构化异常,因为操作系统会捕获它(在 Windows 的情况下)。
  2. 是的,您可以安全地从结构化异常(上面称为“异步”)中恢复,这与上面指出的不同。如果你不能,Windows 根本无法工作。 PAGE_FAULT 是从中恢复的结构化异常。

我不太熟悉 Linux 和其他平台下的工作方式。