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 s开发者_开发知识库trange 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?
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., anoperator new()
replacement). Note thatmalloc()
isn't a 'global allocation function' as defined by the standard, somalloc()
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 allownew
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.
精彩评论