C++ 什么是堆栈展开?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2331316/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:05:19  来源:igfitidea点击:

What is stack unwinding?

c++stack

提问by Rajendra Uppal

What is stack unwinding? Searched through but couldn't find enlightening answer!

什么是堆栈展开?搜索了但找不到有启发性的答案!

采纳答案by Nikolai Fetissov

Stack unwinding is usually talked about in connection with exception handling. Here's an example:

堆栈展开通常与异常处理有关。下面是一个例子:

void func( int x )
{
    char* pleak = new char[1024]; // might be lost => memory leak
    std::string s( "hello world" ); // will be properly destructed

    if ( x ) throw std::runtime_error( "boom" );

    delete [] pleak; // will only get here if x == 0. if x!=0, throw exception
}

int main()
{
    try
    {
        func( 10 );
    }
    catch ( const std::exception& e )
    {
        return 1;
    }

    return 0;
}

Here memory allocated for pleakwill be lost if an exception is thrown, while memory allocated to swill be properly released by std::stringdestructor in any case. The objects allocated on the stack are "unwound" when the scope is exited (here the scope is of the function func.) This is done by the compiler inserting calls to destructors of automatic (stack) variables.

pleak如果抛出异常,这里分配的内存将丢失,而分配给的内存在任何情况下s都会被std::string析构函数正确释放。当作用域退出时,分配在堆栈上的对象被“解开”(这里的作用域是函数)func。这是通过编译器插入对自动(堆栈)变量的析构函数的调用来完成的。

Now this is a very powerful concept leading to the technique called RAII, that is Resource Acquisition Is Initialization, that helps us manage resources like memory, database connections, open file descriptors, etc. in C++.

现在这是一个非常强大的概念,导致了称为RAII的技术,即Resource Acquisition Is Initialization,它帮助我们在 C++ 中管理诸如内存、数据库连接、打开文件描述符等资源。

Now that allows us to provide exception safety guarantees.

现在这允许我们提供异常安全保证

回答by utnapistim

All this relates to C++:

所有这些都与 C++ 相关:

Definition: As you create objects statically (on the stack as opposed to allocating them in the heap memory) and perform function calls, they are "stacked up".

定义:当您静态创建对象(在堆栈上而不是在堆内存中分配它们)并执行函数调用时,它们被“堆叠”。

When a scope (anything delimited by {and }) is exited (by using return XXX;, reaching the end of the scope or throwing an exception) everything within that scope is destroyed (destructors are called for everything). This process of destroying local objects and calling destructors is called stack unwinding.

当一个范围(由{和分隔的任何东西})退出(通过使用return XXX;,到达范围的末尾或抛出异常)时,该范围内的所有东西都被销毁(为所有东西调用析构函数)。这种销毁本地对象和调用析构函数的过程称为堆栈展开。

You have the following issues related to stack unwinding:

您有以下与堆栈展开相关的问题:

  1. avoiding memory leaks (anything dynamically allocated that is not managed by a local object and cleaned up in the destructor will be leaked) - see RAII referred toby Nikolai, and the documentation for boost::scoped_ptror this example of using boost::mutex::scoped_lock.

  2. program consistency: the C++ specifications state that you should never throw an exception before any existing exception has been handled. This means that the stack unwinding process should never throw an exception(either use only code guaranteed not to throw in destructors, or surround everything in destructors with try {and } catch(...) {}).

  1. 避免内存泄漏(任何不受本地对象管理并在析构函数中清理的动态分配的内容都将泄漏) - 请参阅Nikolai引用的RAII ,以及boost::scoped_ptr 的文档或使用boost::mutex 的示例::scoped_lock

  2. 程序一致性:C++ 规范声明在处理任何现有异常之前永远不应抛出异常。这意味着堆栈展开过程不应该抛出异常(要么只使用保证不会抛出析构函数的代码,要么用try {and包围析构函数中的所有内容} catch(...) {})。

If any destructor throws an exception during stack unwinding you end up in the land of undefined behaviorwhich could cause your program to terminate unexpectedly (most common behavior) or the universe to end (theoretically possible but has not been observed in practice yet).

如果任何析构函数在堆栈展开期间抛出异常,您最终会陷入未定义行为的领域,这可能导致您的程序意外终止(最常见的行为)或宇宙结束(理论上可能,但尚未在实践中观察到)。

回答by jrista

In a general sense, a stack "unwind" is pretty much synonymous with the end of a function call and the subsequent popping of the stack.

在一般意义上,堆栈“展开”几乎与函数调用的结束和随后的堆栈弹出同义。

However, specifically in the case of C++, stack unwinding has to do with how C++ calls the destructors for the objects allocated since the started of any code block. Objects that were created within the block are deallocated in reverse order of their allocation.

但是,特别是在 C++ 的情况下,堆栈展开与 C++ 如何为自任何代码块开始以来分配的对象调用析构函数有关。在块内创建的对象以其分配的相反顺序被释放。

回答by Chris Jester-Young

Stack unwinding is a mostly C++ concept, dealing with how stack-allocated objects are destroyed when its scope is exited (either normally, or through an exception).

堆栈展开是一个主要的 C++ 概念,处理堆栈分配的对象在其范围退出时如何销毁(通常或通过异常)。

Say you have this fragment of code:

假设您有以下代码片段:

void hw() {
    string hello("Hello, ");
    string world("world!\n");
    cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"

回答by John Weldon

I don't know if you read this yet, but Wikipedia's article on the call stackhas a decent explanation.

我不知道你是否读过这篇文章,但维基百科关于调用堆栈的文章有一个不错的解释。

Unwinding:

展开:

Returning from the called function will pop the top frame off of the stack, perhaps leaving a return value. The more general act of popping one or more frames off the stack to resume execution elsewhere in the program is called stack unwindingand must be performed when non-local control structures are used, such as those used for exception handling. In this case, the stack frame of a function contains one or more entries specifying exception handlers. When an exception is thrown, the stack is unwound until a handler is found that is prepared to handle (catch) the type of the thrown exception.

Some languages have other control structures that require general unwinding. Pascal allows a global goto statement to transfer control out of a nested function and into a previously invoked outer function. This operation requires the stack to be unwound, removing as many stack frames as necessary to restore the proper context to transfer control to the target statement within the enclosing outer function. Similarly, C has the setjmp and longjmp functions that act as non-local gotos. Common Lisp allows control of what happens when the stack is unwound by using the unwind-protect special operator.

When applying a continuation, the stack is (logically) unwound and then rewound with the stack of the continuation. This is not the only way to implement continuations; for example, using multiple, explicit stacks, application of a continuation can simply activate its stack and wind a value to be passed. The Scheme programming language allows arbitrary thunks to be executed in specified points on "unwinding" or "rewinding" of the control stack when a continuation is invoked.

从被调用函数返回会将栈顶帧弹出,可能会留下一个返回值。从堆栈中弹出一个或多个帧以恢复程序中其他地方的执行的更一般行为称为堆栈展开,并且必须在使用非本地控制结构(例如用于异常处理的控制结构)时执行。在这种情况下,函数的堆栈帧包含一个或多个指定异常处理程序的条目。抛出异常时,堆栈将展开,直到找到准备处理(捕获)抛出异常类型的处理程序。

某些语言具有需要一般展开的其他控制结构。Pascal 允许全局 goto 语句将控制从嵌套函数转移到先前调用的外部函数中。此操作需要展开堆栈,根据需要移除尽可能多的堆栈帧以恢复正确的上下文,从而将控制转移到封闭的外部函数内的目标语句。类似地,C 具有 setjmp 和 longjmp 函数,它们充当非本地 goto。Common Lisp 允许通过使用 unwind-protect 特殊运算符来控制在展开堆栈时发生的情况。

应用延续时,堆栈(逻辑上)展开,然后与延续的堆栈一起重绕。这不是实现延续的唯一方法;例如,使用多个显式堆栈,延续的应用程序可以简单地激活其堆栈并环绕要传递的值。当调用延续时,Scheme 编程语言允许在控制堆栈的“展开”或“回绕”的指定点执行任意 thunk。

Inspection[edit]

检查[编辑]

回答by L. Langó

I read a blog post that helped me to understand.

我读了一篇博文,帮助我理解。

What is stack unwinding?

In any language that supports recursive functions (ie. pretty much everything except Fortran 77 and Brainf*ck) the language runtime keeps a stack of what functions are currently executing. Stack unwinding is a way of inspecting, and possibly modifying, that stack.

Why would you want to do that?

The answer may seem obvious, but there are several related, yet subtly different, situations where unwinding is useful or necessary:

  1. As a runtime control-flow mechanism (C++ exceptions, C longjmp(), etc).
  2. In a debugger, to show the user the stack.
  3. In a profiler, to take a sample of the stack.
  4. From the program itself (like from a crash handler to show the stack).

These have subtly different requirements. Some of these are performance-critical, some are not. Some require the ability to reconstruct registers from outer frame, some do not. But we'll get into all that in a second.

什么是堆栈展开?

在任何支持递归函数的语言中(即除了 Fortran 77 和 Brainf*ck 之外的几乎所有语言),语言运行时都会保存当前正在执行的函数的堆栈。堆栈展开是一种检查并可能修改该堆栈的方法。

你为什么想这么做?

答案似乎很明显,但有几种相关但又略有不同的情况,在这种情况下,展开是有用的或必要的:

  1. 作为运行时控制流机制(C++ 异常、C longjmp() 等)。
  2. 在调试器中,向用户显示堆栈。
  3. 在分析器中,对堆栈进行采样。
  4. 从程序本身(就像从崩溃处理程序来显示堆栈)。

这些要求略有不同。其中一些对性能至关重要,有些则不是。有些需要从外部帧重建寄存器的能力,有些则不需要。但我们马上就会进入这一切。

You can found the full post here.

你可以在这里找到完整的帖子。

回答by Saurav Sahu

IMO, the given below diagram in this articlebeautifully explains the effect of stack unwinding on the route of next instruction (to be executed once an exception is thrown which is uncaught):

IMO时,这在下面图给出文章精美解释堆栈展开下一条指令的路线上的效果(一旦则抛出异常是未捕获的将被执行):

enter image description here

在此处输入图片说明

In the pic:

在图片中:

  • Top one is a normal call execution (with no exception thrown).
  • Bottom one when an exception is thrown.
  • 最上面的一个是正常的调用执行(没有抛出异常)。
  • 抛出异常时的底部。

In the second case, when an exception occurs, the function call stack is linearly searched for the exception handler. The search ends at the function with exception handler i.e. main()with enclosing try-catchblock, but not beforeremoving all the entries before it from the function call stack.

在第二种情况下,当发生异常时,函数调用堆栈会线性搜索异常处理程序。搜索在带有异常处理程序的函数处结束,即main()带有封闭try-catch块,但不是在从函数调用堆栈中删除它之前的所有条目之前。

回答by bbv

Everyone has talked about the exception handling in C++. But,I think there is another connotation for stack unwinding and that is related to debugging. A debugger has to do stack unwinding whenever it is supposed to go to a frame previous to the current frame. However, this is sort of virtual unwinding as it needs to rewind when it comes back to current frame. The example for this could be up/down/bt commands in gdb.

大家都讲过C++中的异常处理。但是,我认为堆栈展开还有另一个含义,这与调试有关。每当它应该转到当前帧之前的帧时,调试器必须进行堆栈展开。然而,这是一种虚拟的展开,因为当它回到当前帧时需要倒带。这个例子可以是 gdb 中的 up/down/bt 命令。

回答by DigitalEye

C++ runtime destructs all automatic variables created between between throw & catch. In this simple example below f1() throws and main() catches, in between objects of type B and A are created on the stack in that order. When f1() throws, B and A's destructors are called.

C++ 运行时会破坏在 throw 和 catch 之间创建的所有自动变量。在下面的这个简单示例中,f1() 抛出和 main() 捕获,在 B 和 A 类型的对象之间按该顺序在堆栈上创建。当 f1() 抛出时,调用 B 和 A 的析构函数。

#include <iostream>
using namespace std;

class A
{
    public:
       ~A() { cout << "A's dtor" << endl; }
};

class B
{
    public:
       ~B() { cout << "B's dtor" << endl; }
};

void f1()
{
    B b;
    throw (100);
}

void f()
{
    A a;
    f1();
}

int main()
{
    try
    {
        f();
    }
    catch (int num)
    {
        cout << "Caught exception: " << num << endl;
    }

    return 0;
}

The output of this program will be

这个程序的输出将是

B's dtor
A's dtor

This is because the program's callstack when f1() throws looks like

这是因为当 f1() 抛出时程序的调用堆栈看起来像

f1()
f()
main()

So, when f1() is popped, automatic variable b gets destroyed, and then when f() is popped automatic variable a gets destroyed.

因此,当 f1() 被弹出时,自动变量 b 被销毁,然后当 f() 被弹出时自动变量 a 被销毁。

Hope this helps, happy coding!

希望这会有所帮助,编码愉快!

回答by MK.

When an exception is thrown and control passes from a try block to a handler, the C++ run time calls destructors for all automatic objects constructed since the beginning of the try block. This process is called stack unwinding. The automatic objects are destroyed in reverse order of their construction. (Automatic objects are local objects that have been declared auto or register, or not declared static or extern. An automatic object x is deleted whenever the program exits the block in which x is declared.)

当抛出异常并且控制从 try 块传递到处理程序时,C++ 运行时会为自 try 块开始构造的所有自动对象调用析构函数。这个过程称为堆栈展开。自动对象以其构造的相反顺序被销毁。(自动对象是已声明为 auto 或 register,或未声明为 static 或 extern 的本地对象。只要程序退出声明 x 的块,就会删除自动对象 x。)

If an exception is thrown during construction of an object consisting of subobjects or array elements, destructors are only called for those subobjects or array elements successfully constructed before the exception was thrown. A destructor for a local static object will only be called if the object was successfully constructed.

如果在构造由子对象或数组元素组成的对象期间抛出异常,则仅对在抛出异常之前成功构造的那些子对象或数组元素调用析构函数。只有在成功构造对象时才会调用本地静态对象的析构函数。