36

在 C++ 中,堆栈溢出通常会导致程序不可恢复的崩溃。对于需要真正健壮的程序,这是一种不可接受的行为,特别是因为堆栈大小是有限的。关于如何处理问题的几个问题。

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

  2. 有没有办法处理堆栈溢出以防它们发生?最好,堆栈会展开,直到有处理程序来处理这种问题。

  3. 那里有一些语言,它们具有带有可扩展堆栈的线程。类似的东西在 C++ 中可能吗?

任何其他关于 C++ 行为解决方案的有用评论将不胜感激。

4

6 回答 6

34

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

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

一个必须“真正健壮”的程序不会使用“吃很多堆栈”的第三方或外部库。


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

问题是堆栈溢出可能会导致异步异常被抛出,即使是从不期望抛出任何异常的代码(例如,从标记的函数noexceptthrow()在 C++ 中)。因此,即使您确实以某种方式处理了此异常,您也无法知道您的程序处于安全状态。因此,处理异步异常的最佳方法是根本不处理(*)。如果抛出一个,则意味着程序包含错误。

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

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

于 2012-08-27T17:20:47.917 回答
10

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

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

这些是我在过去几年中看到的最常见的原因。

对于自动 SO 查找,您应该能够找到一些静态代码分析工具。

于 2012-08-27T17:42:21.547 回答
4

回复:可扩展堆栈。您可以通过以下方式给自己更多的堆栈空间:

#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 的汇编语法。

于 2012-08-27T18:02:33.347 回答
2

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

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

于 2012-08-27T17:20:48.837 回答
0

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

于 2021-01-21T07:00:26.607 回答
-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 和其他平台下的工作方式不太熟悉。

于 2021-02-15T04:37:33.740 回答