Head's up: this is a way geeky question. It's about C++ calling conventions and exception handling. (Is it bad form for me to use this blog as my own personal help forum?)

I have a strange problem where C++ destructors are not being called for stack-allocated objects when an exception is thrown through my function. For example:

class StackThingy
{
public:
    // Constructor will be called when the StackThingy
    // is created on the stack.
    StackThingy()
    {
        // (Point A)
    }

    // Destructor should be called when the StackThingy
    // is unwound from the stack.
    ~StackThingy()
    {
        // (Point B)
    }
};

void __stdcall
MyTestRoutine()
{
    // Create the StackThingy
    StackThingy thingy;

    // Throw an exception.  This should unwind the StackThingy
    // object, calling its destructor.
    throw CException();
}

When I run this code, Point A executes in the constructor, but execution never reaches Point B. It should, because the exception being thrown should unwind stack, calling the destructors on any stack-allocated object. But somehow, the exception would not properly destroy the thingy object.

I narrowed it down to the __stdcall calling convention. If I remove the __stdcall declaration to make it a regular C++ function, then the destructor is called as I would expect. I can find no mention of this behavior in the docs or on the wider web. The description of __stdcall talks about stack maintenance as a key difference between calling conventions, but doesn't describe exception handling at all.

Is this expected behavior, or is there some other nefarious factor at work here?

tagged: , » 9 reactions

Comments

[gravatar]
Mike G. 9:11 AM on 29 Jul 2004

Interesting... Does the throw actually occur? What happens if you wrap your call to MyTestRoutine in a try/catch block? Or add a debugging print statement after the throw?

It'd be shocking if throw turns into a nop in this case, but not much less shocking than the destructor not being called...

I had no luck finding information about this problem either.

BTW, what happens if an __stdcall routine is in the middle of a "normal" call chain, and the lower "normal" function throws? Does unwinding happen correctly across the __stdcall?

Obviously, I should test all these questions myself, but Visual C++ seems to be missing from this Linux box :)

[gravatar]
Sylvain Galineau 9:21 AM on 29 Jul 2004

I haven't messed with this in too long but I think __stdcall has the callee cleaning up the stack. If that's the case, could this conflict with the back-end unwinding logic by removing from the frame what it needs to walk up the chain of destructors inside the caller ? If the default calling convention has the caller cleaning up the stack, we can assume something is being removed that shouldn't be.

I'd suggest e-mailing Raymond Chen...C++ exception handling and how it maps to/works with Win32's Structure Exception Handling is a rather complex realm.

[gravatar]
Ned Batchelder 9:46 AM on 29 Jul 2004

It looks like I haven't told the whole story: the code is behaving as I described, but it was due to an ' extern "C" ' declaration on the function.

The throw really occurs, and is caught by a calling function. The odd thing is that destructors are called for the stack objects if the function exits normally, but not if an exception is thrown.

[gravatar]
andrew 3:46 PM on 29 Jul 2004

This sounds like a job for Bob. After all, he is one of the few people I know that actually uses "trigraph" in a sentence.

[gravatar]
Bob Balaban 7:01 PM on 29 Jul 2004

What if you do the decl as StackThingy thingy();
i.e., with the parens? Does that change the code flow at all?

[gravatar]
Ned Batchelder 7:12 PM on 29 Jul 2004

I've never seen a difference with parens or without; it's supposed to be the same either way. Of course, this whole topic is about a C++ compiler dropping the ball on C++ semantics, so all bets are off.

[gravatar]
Bob 11:03 PM on 29 Jul 2004

I tried to recreate the problem but I don't see it. Is it possible that there's a calling convention issue between source files? This is just plain weird.

[gravatar]
Jonathon Duerig 2:04 AM on 30 Jul 2004

To be more specific, could it be that the function declaration is declared __stdcall and the function definition it is not? Or vice versa? I could envision this problem being caused by the resulting linking issues.

Note that the Microsoft docs don't explicitly say that the definition and declaration both include the __stdcall keyword, but the fact that __stdcall changes the calling conventions makes it seem likely that both are required.

[gravatar]
Sylvain Galineau 2:40 PM on 31 Jul 2004

Wow. That's a different story. The behavior of that is undefined. See here : "/GX enables support for synchronous (also called typed or C++) exception handling with the assumption that extern C functions don't throw C++ exceptions. "

Later on....:

"Finally, let's consider whether we can assume that extern "C" functions don't throw C++ exceptions (remember that such an assumption is built into /GX). Extern "C" functions in a C++ program are most likely being linked in from an object module or library. These functions are often written in a different language, such as C or assembler. Because the type of exception we are handling is a feature specifically of the C++ language only, it seems a reasonable assumption to say that an extern "C" function will not throw an exception.

Of course, if an extern "C" function is compiled by the C++ compiler, it is perfectly legal for that function, or one of its called functions, to throw a C++ exception. For those who must throw C++ exceptions from extern "C" functions, replace /GX, which is the same as using /EHsc, with just /EHs. /EHs enables C++ exception handling without the assumption that extern "C" functions will never throw a C++ exception. /EHc enables the assumption that extern "C" functions do not throw exceptions."

Let us know if this helps...

Add a comment:

name
email
Ignore this:
not displayed and no spam.
Leave this empty:
www
not searched.
 
Name and either email or www are required.
Don't put anything here:
Leave this empty:
URLs auto-link and some tags are allowed: <a><b><i><p><br><pre>.