Exception handling showing undefined behaviour. Help !!!

Question #1 :

The following code crashes in Visual C++ compiler. It fails in the expression _BLOCK_TYPE_IS_VALID(). It means that, it is deleting a bad pointer.

When I give a copy constructor, it works fine but the control does not even go in the copy constructor. Why is it showing this behaviour

Question #2 :

If I throw a local primitive type like an int, the address of that variable matches in the catch block if I am catching by reference. But for objects, the address for the variable in the catch block changes. Why

#include <iostream>

using namespace std ;

class Exception

{

char *m_pszError ;

public:

Exception(const char *p)

{

cout << "Exception::Exception()" << endl ;

m_pszError = new char[strlen(p) + 1] ;

strcpy(m_pszError, p) ;

}

~Exception()

{

cout <<"Exception::~Exception()"<< endl ;

delete []m_pszError ;

}

} ;

int main()

{

try

{

throw Exception("Test") ;

}

catch(Exception & ex)

{

}

return 0 ;

}



Answer this question

Exception handling showing undefined behaviour. Help !!!

  • Philippe Cand

    I like your explanation and that's what we observed. The bigger question is: why doesn't this happen when an explicit copy constructor is not available. Why does the compiler use the default copy constructor and create the bitwise copy


  • Sparx

    Yeah, I figured you would get back about that. First off, it is the programmers at Microsoft that made the compiler smart. And I hope they jump in to this thread to explain this. It is hard to find out, the CRT source code doesn't include the exception handling code, just eh.lib and it is large.

    I tried lots of different ways to make it use the copy constructor but couldn't. The only thing that makes sense is that the stack unwinding code unwinds frames (adjusts the BP-register) but not the stack (the SP-register). That keeps the Exception object valid on the stack.

    Another way to look at it is that they forgot to make the compiler smart when there is no copy constructor. I'm sure there's some good reason...



  • Onslaught_2000

    If the Exception class has no copy constructor, but has a member variable which implement the copy ctor, things also work just fine. Why's that

  • John Mears

    See sections 15.1/5 of the standard.

    My guess is that in the presence of an accessible copy constructor and destructor, the compiler can eliminate the use of a temporary object. The copy is therefore actually never done, but it's existence indirectly cause the destructor to be called only once.

    What I find odd is how the Exception class having an instance of another class (which has a ctor, copy ctor and dtor) seems to eliminate the temporary object (and thus destruction error) -- even without a copy ctor in the Exception class.



  • errolian

    When you throw Exception, it creates an Exception instance on the stack frame. Normally, the CRT unwinds stack frames to reach the catch handler so the Exception instance becomes invalid. To deal with that, the compiler automatically creates a new instance by using the copy constructor. This code doesn't have one so it uses the default one that just does a bitwise copy of the class members.

    Now you have two instances, each has an m_pszError pointing to the heap block that contains "Test". The first one gets destructed just before the stack unwinds and deletes the heap block. The second one gets destructed after the catch handler executes and deletes the same heap block again. Boom.

    When you provide the copy constructor, you won't create the duplicate pointers to the heap block. It also makes the compiler smart for some reason and it doesn't make the instance copy, that's why you don't get the break in the debugger. Note that the destructor only runs once, not twice.

    I'm not sure why the address wouldn't change for a primitive type, other than the compiler being smart enough to re-use the stack frame address, but it changes when you throw the exception in another function. I'm not 100% sure if I got the details right but this has been my interpretation of what I've seen...




  • mshvw

    Thanks for your kind reply.

    But there is still some ambiguity about the way compiler manages it. You have written

    It also makes the compiler smart for some reason and it doesn't make the instance copy, that's why you don't get the break in the debugger

    What does this mean How does the compiler manage it What is making the compiler smart

    Once again, thanks.


  • Alchemy_Mgt

    What you're observing there is simply a quirk of the compiler - for whatever reason, it is able to elide the copy in some cases and not others. It's likely the result of a strategy that does a bitwise copy of POD classes to save some other processing.

    In the case that crashes, the compiler is concluding the that class is POD since it has no copy constructor, while the presense of the copy constructor lets the compiler know that the class is not POD so it uses a different strategy - one that doesn't actually require copying the object but probably has some other cost(s) associated with it (otherwise why not just use that stragety in all cases ).



  • Scott Croisdale

    Each time you allocate memory dynamically inside a class/struct, you have to provide a copy constructor and an assignment operator, because the ones the compiler automatically creates are doing a shallow copy, and you need a deep copy.

    Moreover, why don't you simply use std::string instead of char* and avoid doing any memory allocation/releasing by yourself



  • ctrahan

    Here's the trick: When you catch a C++ exception, the stack frame is set up as-if the catch block was called from the location of the throw.

    That's how the original object address at the throw site can still exist at the catch site - that part of the stack is still active! IIRC, destructors on objects allocated in the "unwound" stack frames will have already run at the point of the catch, but the stack pointer itself isn't adjusted until after the catch block executes.

    Of course, such details are completely implementation defined and could change at any time. I don't know if this situation is the same for x64, for example. I suspect that it's not, because the whole approach to exceptions in the x64 compiler is different (it uses a table-based approach rather than the frame-based approach that the x86 compiler uses).

    The Standard requires that the class have an accessible copy-constructor. It doesn't require that the compiler uses it.



  • brother martin

    *bump*, I'm really curious...



  • alwz_nikhil

    *bump*. Ayman, can you help I'd like to know because I had exactly the same problem...



  • Exception handling showing undefined behaviour. Help !!!