简体   繁体   English

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

[英]How to handle or avoid a stack overflow in C++

In C++ a stack overflow usually leads to an unrecoverable crash of the program.在 C++ 中,堆栈溢出通常会导致程序不可恢复的崩溃。 For programs that need to be really robust, this is an unacceptable behaviour, particularly because stack size is limited.对于需要真正健壮的程序,这是不可接受的行为,特别是因为堆栈大小是有限的。 A few questions about how to handle the problem.关于如何处理问题的几个问题。

  1. Is there a way to prevent stack overflow by a general technique.有没有办法通过通用技术防止堆栈溢出。 (A scalable, robust solution, that includes dealing with external libraries eating a lot of stack, etc.) (一个可扩展的、健壮的解决方案,包括处理消耗大量堆栈的外部库等)

  2. Is there a way to handle stack overflows in case they occur?如果发生堆栈溢出,有没有办法处理它们? Preferably, the stack gets unwound until there's a handler to deal with that kinda issue.最好是,在有一个处理程序来处理那个问题之前,堆栈会被解开。

  3. There are languages out there, that have threads with expandable stacks.有一些语言具有可扩展堆栈的线程。 Is something like that possible in C++?在 C++ 中可能有这样的事情吗?

Any other helpful comments on the solution of the C++ behaviour would be appreciated.对 C++ 行为的解决方案的任何其他有用的评论将不胜感激。

Handling a stack overflow is not the right solution, instead, you must ensure that your program does not overflow the stack.处理堆栈溢出不是正确的解决方案,相反,您必须确保您的程序不会溢出堆栈。

Do not allocate large variables on the stack (where what is "large" depends on the program).不要在堆栈上分配大变量(什么是“大”取决于程序)。 Ensure that any recursive algorithm terminates after a known maximum depth.确保任何递归算法在已知最大深度后终止。 If a recursive algorithm may recurse an unknown number of times or a large number of times, either manage the recursion yourself (by maintaining your own dynamically allocated stack) or transform the recursive algorithm into an equivalent iterative algorithm如果递归算法可能递归未知次数或大量次数,请自行管理递归(通过维护自己的动态分配堆栈)或将递归算法转换为等效的迭代算法

A program that must be "really robust" will not use third-party or external libraries that "eat a lot of stack."一个必须“非常健壮”的程序不会使用“占用大量堆栈”的第三方或外部库。


Note that some platforms do notify a program when a stack overflow occurs and allow the program to handle the error.请注意,某些平台会在发生堆栈溢出时通知程序并允许程序处理错误。 On Windows, for example, an exception is thrown.例如,在 Windows 上,会引发异常。 This exception is not a C++ exception, though, it is an asynchronous exception.此异常不是 C++ 异常,但它是异步异常。 Whereas a C++ exception can only be thrown by a throw statement, an asynchronous exception may be thrown at any time during the execution of a program. C++ 异常只能由throw语句throw ,而异步异常可能在程序执行过程中的任何时候抛出。 This is expected, though, because a stack overflow can occur at any time: any function call or stack allocation may overflow the stack.但是,这是意料之中的,因为堆栈溢出随时可能发生:任何函数调用或堆栈分配都可能使堆栈溢出。

The problem is that a stack overflow may cause an asynchronous exception to be thrown even from code that is not expected to throw any exceptions (eg, from functions marked noexcept or throw() in C++).问题是堆栈溢出可能导致异步异常被抛出,即使是从预期不会抛出任何异常的代码(例如,来自 C++ 中标记为noexceptthrow()函数)。 So, even if you do handle this exception somehow, you have no way of knowing that your program is in a safe state.因此,即使您确实以某种方式处理了此异常,您也无法知道您的程序处于安全状态。 Therefore, the best way to handle an asynchronous exception is not to handle it at all (*) .因此,处理异步异常的最佳方法是根本不处理(*) If one is thrown, it means the program contains a bug.如果抛出一个,则意味着程序包含错误。

Other platforms may have similar methods for "handling" a stack overflow error, but any such methods are likely to suffer from the same problem: code that is expected not to cause an error may cause an error.其他平台可能有类似的方法来“处理”堆栈溢出错误,但任何此类方法都可能遇到相同的问题:预期不会导致错误的代码可能会导致错误。

(*) There are a few very rare exceptions. (*) 有一些非常罕见的例外。

You can protect against stack overflows using good programming practices, like:您可以使用良好的编程实践来防止堆栈溢出,例如:

  1. Be very carefull with recursion, I have recently seen a SO resulting from badly written recursive CreateDirectory function, if you are not sure if your code is 100% ok, then add guarding variable that will stop execution after N recursive calls.对递归要非常小心,我最近看到了由于递归 CreateDirectory 函数编写不当而导致的 SO,如果您不确定您的代码是否 100% 正常,则添加保护变量,该变量将在 N 次递归调用后停止执行。 Or even better dont write recursive functions.或者最好不要写递归函数。
  2. Do not create huge arrays on stack, this might be hidden arrays like a very big array as a class field.不要在堆栈上创建巨大的数组,这可能是隐藏的数组,就像一个非常大的数组作为类字段。 Its always better to use vector.使用矢量总是更好。
  3. Be very carefull with alloca, especially if it is put into some macro definition.使用 alloca 时要非常小心,尤其是当它被放入一些宏定义时。 I have seen numerous SO resulting from string conversion macros put into for loops that were using alloca for fast memory allocations.我已经看到很多 SO 是由字符串转换宏放入 for 循环中产生的,这些循环使用 alloca 进行快速内存分配。
  4. Make sure your stack size is optimal, this is more important in embeded platforms.确保您的堆栈大小是最佳的,这在嵌入式平台中更为重要。 If you thread does not do much, then give it small stack, otherwise use larger.如果你的线程做的不多,那么给它小堆栈,否则使用更大的堆栈。 I know reservation should only take some address range - not physical memory.我知道保留应该只占用一些地址范围 - 而不是物理内存。

those are the most SO causes I have seen in past few years.这些是我在过去几年中看到的最SO原因。

For automatic SO finding you should be able to find some static code analysis tools.对于自动 SO 查找,您应该能够找到一些静态代码分析工具。

Re: expandable stacks.回复:可扩展堆栈。 You could give yourself more stack space with something like this:你可以用这样的东西给自己更多的堆栈空间:

#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;
}

This is gcc's assembler syntax.这是 gcc 的汇编语法。

C++ is a powerful language, and with that power comes the ability to shoot yourself in the foot. C++ 是一种强大的语言,有了这种强大的能力,你就可以用脚射击自己了。 I'm not aware of any portable mechanism to detect and correct/abort when stack overflow occurs.我不知道有任何便携式机制可以在发生堆栈溢出时检测和纠正/中止。 Certainly any such detection would be implementation-specific.当然,任何此类检测都是特定于实现的。 For example g++ provides -fstack-protector to help monitor your stack usage.例如,g++ 提供-fstack-protector来帮助监控您的堆栈使用情况。

In general your best bet is to be proactive in avoiding large stack-based variables and careful with recursive calls.一般来说,最好的办法是主动避免基于堆栈的大型变量并小心递归调用。

I don't think that that would work.我不认为那会奏效。 It would be better to push/pop esp than move to a register because you don't know if the compiler will decide to use eax for something. push/pop esp 比移动到寄存器更好,因为你不知道编译器是否会决定使用 eax 来做某事。

Here ya go: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/resetstkoflw?view=msvc-160在这里你去: https : //docs.microsoft.com/en-us/cpp/c-runtime-library/reference/resetstkoflw?view=msvc-160

  1. You wouldn't catch the EXCEPTION_STACK_OVERFLOW structured exception yourself because the OS is going to catch it (in Windows' case).您不会自己捕获 EXCEPTION_STACK_OVERFLOW 结构化异常,因为操作系统将捕获它(在 Windows 的情况下)。
  2. Yes, you can safely recover from an structured exception (called "asynchronous" above) unlike what was indicated above.是的,您可以安全地从结构化异常(上面称为“异步”)中恢复,这与上面指出的不同。 Windows wouldn't work at all if you couldn't.如果你不能,Windows 根本无法工作。 PAGE_FAULTs are structured exceptions that are recovered from. PAGE_FAULT 是从中恢复的结构化异常。

I am not as familiar with how things work under Linux and other platforms.我不太熟悉 Linux 和其他平台下的工作方式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM