Recently I ran into a problem that I traced to something which completely
surprised me: the relative order of stack unwinding and finally block
execution in mixed C++ appears to vary depending on how execution leaves the
corresponding try block.
Consider the following abbreviated code:
// some unmanaged class
__nogc class Foo
{
public:
~Foo();
};
// some managed function
void func()
{
Foo f;
try
{
// something
if ( condition1 )
{
throw __gc new Exception();
}
else if ( condition2 )
{
return;
}
}
__finally
{
// clean up
}
}
Given the scope of "f", I would expect the code in the __finally block to
execute before f's destructor is called. That's exactly what happens if the
code in the try block either runs through to the end, or throws an
exception. But if the "return" statement in the try block is executed, this
order is reversed, and ~Foo() runs *before* the __finally block. I did not
expect this!
Looking at the generated IL, this appears to be deliberate -- the "return"
is compiled as an explicit call tothe dtor followed by a "leave". But this
doesn't seem valid -- "f" is in scope when the __finally code executes, so
shouldn't it be guranteed to be valid (non-destructed) at that point? As it
is, __the finally code can easily access and use a destructed (though
presumably not deallocated) object, which shouldn't be possible. Unless it's
documented as a special case, it seem flat-out wrong.
The behavior is the same in Managed C++ (CLR 1.1) and the latest Whidbey
C++/CLI RC. I can't find any documentation addressing this. If anyone has
any insights or thoughts, please share them. I'm curious if this was done
for a good reason, and if it is documented anywhere, how it's justified,
etc.
Thanks