views:

157

answers:

6

In the following code, the stack-based variable 'ex' is thrown and caught in a function beyond the scope in which ex was declared. This seems a bit strange to me, since (AFAIK) stack-based variables cannot be used outside the scope in which they were declared (the stack is unwound).

void f() {
    SomeKindOfException ex(...);
    throw ex;
}

void g() {
    try {
        f();
    } catch (SomeKindOfException& ex) {
        //Handling code...
    }
}

I've added a print statement to SomeKindOfException's destructor and it shows that ex is destructed once it goes out of scope in f() but then it's caught in g() and destructed again once it goes out of scope there as well.

Any help?

+10  A: 

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.

Anders Abel
Oh, this is what I expected intuitively, I'm glad it corresponds to truth. Also the copy constructor should be called, so how do you properly deal with this?Suppose your exception class have an instance variable that contains the error message: at every subsequent `throw` you have a new copy allocation of that variable!Speaking of best practice, what do you think about ensuring shallow copy only for all exceptions?
Dacav
+6  A: 

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).

Johannes Schaub - litb
Thanks for clarifying the memory location of the exception, I've edited my comment above to remove that question.
John Doe
+1  A: 

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

Kerido
+3  A: 

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.

Kirill V. Lyadvinsky
+4  A: 

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.

Matteo Italia
Using VC++, the memory location of the thrown object is very close to that of the original object (i.e, on the stack). This really kind of proves Johannes and Kirill's point, that the memory location in unspecified.
John Doe
You're right, this is really an implementation detail. I'll clarify the answer.
Matteo Italia
A: 

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.

Michael Burr