I want to figure out where my thrown object is stored in memory. So I wrote a tiny program for it:
#include <iostream>
#define print_dist() int a;\
do { std::cout << __FUNCTION__ << "() a[" << (long)&a - (long)ptrMainStackBase << "]" << std::endl; } while (0)
#define print_distx(x) \
do { std::cout << __FUNCTION__ << "() " #x "[" << (long)&x - (long)ptrMainStackBase << "]" << std::endl; } while (0)
#define print_distxy(x, y) \
do { std::cout << __FUNCTION__ << "() " #x "(ex)[" << (long)&x - (long)y << "]" << std::endl; } while (0)
class CTest
{
public:
CTest()
{ std::cout << "CTest::CTest" << std::endl; }
// private:
CTest(const CTest&)
{ std::cout << "copy" << std::endl; }
};
const CTest *ptrException;
int *ptrMainStackBase;
void Test2()
{
print_dist();
CTest test;
print_distx(test);
std::cout << "&test=" << &test << std::endl;
throw test;
}
void Test1()
{
print_dist();
try
{
Test2();
}
catch (const CTest& test)
{
ptrException = &test;
print_dist();
print_distx(test);
print_distxy(test, ptrException);
std::cout << "&test=" << &test << std::endl;
throw test;
}
}
int main()
{
int b;
ptrMainStackBase = &b;
print_dist();
try
{
print_dist();
Test1();
}
catch (const CTest& test)
{
print_dist();
print_distx(test);
print_distxy(test, ptrException);
std::cout << "&test=" << &test << std::endl;
}
return 0;
}
and it prints:
main() a[-4]
main() a[-8]
Test1() a[-64]
Test2() a[-104]
CTest::CTest
Test2() test[-108]
&test=0x7fffd3b21628 <- test created here on stack
copy
Test1() a[-68]
Test1() test[-140736732956164]
Test1() test(ex)[0]
&test=0xb89090 <- and copied here
copy
main() a[-12]
main() test[-140736732956020]
main() test(ex)[144]
&test=0xb89120 <- and here
It looks like, when I throw an object, it is first copied to another stack which is far away from the normal one. Is this true? And why there are 144 byte distance between the two "exception stack frames"?
The throw statement requires a single argument: a throwable object. Throwable objects are instances of any subclass of the Throwable class.
The throw statement can throw various types of objects. Objects in C++ may generally be thrown by value, reference, or pointer. For example: // throw an object to be caught by value or reference throw EIntegerRange(0, 10, userValue); // throw an object to be caught by pointer throw new EIntegerRange(0, 10, userValue);
The throw keyword throws an exception when a problem is detected, which lets us create a custom error. The catch statement allows you to define a block of code to be executed, if an error occurs in the try block.
throw usually causes the function to terminate immediately, so you even if you do put any code after it (inside the same block), it won't execute. This goes for both C++ and C#.
When you throw an object it is indeed copied first to some temporary location. Otherwise, the stack unrolling would have taken it out of scope. Catching it then by reference would have resulted in undefined behavior, like so:
void foo() {
A a;
throw a;
}
void bar() {
try {
foo();
} catch (A& a) {
// use a
}
}
Unless a
had been copied to some temporary location, you'd have a reference to a variable that no longer exists in the catch
. For this to work, A
must have a public copy constructor (unless you're using VS, in which case it will use a private one as well...). Also, this is a good reason to catch by reference - otherwise, you'll have two copy constructions instead of one.
It looks like, when I throw an object it is copied first to an other stack which is far away from the normal one. Is this true?
The test
that was thrown does not exist when the exception is caught. That original test
has already been destructed. So it has to be a copy, and new object managed separately from arguments and local variables. In other words, not on the stack.
Where it lives? That's up to the implementation. Most likely it's dynamic memory (e.g., the heap) that the implementation manages for you.
And why there are 144 byte distance between the two "exception stack frame"?
The standard doesn't say how an implementation is to treat an exception when it is re-thrown in a catch
block. Since the throw
of a local variable must necessarily make a copy, the easiest way to implement throw
is to always make a copy.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With