How are exceptions allocated on the stack caught beyond their scope?

我是研究僧i 提交于 2019-11-28 09:00:13

The exception object is copied to a special location to survive the stack unwinding. The reason you see two destructions is because when you exit f() the original exception is destroyed and when you exit g() the copy is destroyed.

The object is copied into an exception object that survives stack-unwinding. Where the memory for that object comes from is unspecified. For big object, it will probably be malloc'ed, and for smaller objects, the implementation could have a pre-allocated buffer (i could imagine this could be used for a bad_alloc exception).

The reference ex is then bound to that exception object, which is a temporary (it has no name).

C++ Standard 15.1/4:

The memory for the temporary copy of the exception being thrown is allocated in an unspecified way, except as noted in 3.7.3.1. The temporary persists as long as there is a handler being executed for that exception. In particular, if a handler exits by executing a throw; statement, that passes control to another handler for the same exception, so the temporary remains. When the last handler being executed for the exception exits by any means other than throw; the temporary object is destroyed and the implementation may deallocate the memory for the temporary object; any such deallocation is done in an unspecified way. The destruction occurs immediately after the destruction of the object declared in the exception-declaration in the handler.

There is nothing more to say.

When you throw ex it is copied in a special memory location used for thrown exception objects. Such copy is carried out by the normal copy constructor.

You can see this easily from this example:

#include <iostream>

void ThrowIt();

class TestException
{
  public:
    TestException()
    {
        std::cerr<<this<<" - inside default constructor"<<std::endl;
    }

    TestException(const TestException & Right)
    {
        (void)Right;
        std::cerr<<this<<" - inside copy constructor"<<std::endl;
    }

    ~TestException()
    {
        std::cerr<<this<<" - inside destructor"<<std::endl;    
    }
};

int main()
{
    try
    {
        ThrowIt();
    }
    catch(TestException & ex)
    {
        std::cout<<"Caught exception ("<<&ex<<")"<<std::endl;
    }
    return 0;
}

void ThrowIt()
{
    TestException ex;
    throw ex;
}

Sample output:

matteo@teolapubuntu:~/cpp/test$ g++ -O3 -Wall -Wextra -ansi -pedantic ExceptionStack.cpp -o ExceptionStack.x
matteo@teolapubuntu:~/cpp/test$ ./ExceptionStack.x 
0xbf8e202f - inside default constructor
0x9ec0068 - inside copy constructor
0xbf8e202f - inside destructor
Caught exception (0x9ec0068)
0x9ec0068 - inside destructor

By the way, you can see here that the memory location used for the thrown object (0x09ec0068) is definitely far away from the one of the original object (0xbf8e202f): the stack, as usual, has high addresses, while the memory used for the thrown object is quite down in the virtual address space. Still, this is an implementation detail, since, as other answers pointed out, the standard do not say anything about where the memory for thrown object should be and how should it be allocated.

In addition to what the standard says in 15.1/4 ("Exception handling/Throwing an exception") - that the memory for the temporary copy of the exception being thrown is allocated in an unspecified way - a couple other bits of trivia about how the exception object is allocated are:

  • 3.7.3.1/4 ("Allocation functions") of the standard indicates that the exception object can't be allocated by a new expression or a call to a 'global allocation function' (ie., an operator new() replacement). Note that malloc() isn't a 'global allocation function' as defined by the standard, so malloc() is definitely an option for allocating the exception object.

  • "When an exception is thrown, the exception object is created and an placed generally on some sort of exception data stack" (Stanley Lippman, "Inside the C++ Object Model" - 7.2 Exception handling)

  • From Stroustrup's "The C++ Programming Language, 3rd Edition": "A C++ implementation is required to have enough spare memory to be able to throw bad_alloc in case of memory exhaustion. However, it is possible that throwing some other exception will cause memory exhaustion."(14.4.5 Resource Exhaustion); and, "The implementation may apply a wide variety of strategies for storing and transmitting exceptions. It is guaranteed, however, that there is sufficient memory to allow new to throw the standard out-of-memory exception, bad_alloc" (14.3 Catching exceptions).

Note that the quotes from Stroustrup are pre-standard. I find it interesting that the standard doesn't seem to make the guarantee that Stroustrup thought important enough to mention twice.

Because the specification explicitly states, that a temporary object is created in place of the throw operand.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!