views:

357

answers:

7

I know that my destructors are called on normal unwind of stack and when exceptions are thrown, but not when exit() is called.

Are there any other cases where my destructors are not going to get called? What about signals such as SIGINT or SIGSEGV? I presume that for SIGSEGV, they are not called, but for SIGNINT they are, how do I know which signals will unwind the stack?

Are there any other circumstances where they will not be called?

+2  A: 

abort terminates program without executing destructors for objects of automatic or static storage duration as Standard says. For other situations you should read implementation specific documents.

Kirill V. Lyadvinsky
+2  A: 

A signal by itself won't affect the execution of the current thread and hence the invocation of destructors, because it is a different execution context with its own stack, where your objects do not exist. It's like an interrupt: it is handled somewhere outside of your execution context, and, if handled, the control is returned to your program.

Same as with multithreading, C++ the language does not know a notion of signals. These two are completely orthogonal to each other and are specified by two unrelated standards. How they interact is up to the implementation, as long as it does not break either of the standards.

As a side note, another case is when object's destructor won't be called is when its constructor throws an exception. Members' destructors will still be called, though.

Alex B
+5  A: 

The C++ standard says nothing about how specific signals must be handled - many implementations may not support SIGINT etc. Destructors wil not be called if exit() or abort() or terminate() are called.

Edit: I've just had a quick search through the C++ Standard and I can't find anything that specifies how signals interact with object lifetimes - perhaps someone with better standards-fu than me could find something?

Further edit: While answering another question, I found this in the Standard:

On exit from a scope (however accomplished), destructors (12.4) are called for all constructed objects with automatic storage duration (3.7.2) (named objects or temporaries) that are declared in that scope, in the reverse order of their declaration.

So it seems that destructors must be called on receipt of a signal.

anon
On receipt of a signal, program control does not exit scope. So the quoted standard does not apply. The default behavior of POSIX signal handlers does not do any stack unwinding or destruction.
karunski
@karunski It certainly exits the scope if a signal handler is installed.
anon
@Neil Butterworth Sure, but that doesn't happen by default. The more correct conclusion would be "Destructors must be called after a signal is handled (not received) and control returns to the point where the signal handler was invoked"
karunski
@karunski Do you have a Standard reference for the behaviour when a handler is not installed?
anon
@Neil Butterworth Here's the POSIX standard signal handlers: http://opengroup.org/onlinepubs/007908775/xsh/signal.h.html
karunski
@karunski POSIX does not define the C++ language.
anon
@Neil Butterworth The C++ language standard does not define signal behavior at all. POSIX does, and in practice unix-like operating systems follow POSIX.
karunski
@karunski But the question is about C++.
anon
What's your point? The question is also about signals.
karunski
+1  A: 

There are basically two situations, where destructors are called: On stack unwind at the end of function (or at exceptions), if someone (or a reference counter) calls delete.

One special situation is to be found in static objects - they are destructed at the end of the program via at_exit, but this is still the 2nd situation.

Which signal leaves at_exit going through may depend, kill -9 will kill the process immediately, other signals will tell it to exit but how exactly is dependent on the signal callback.

nob
+1  A: 

Another case they won't be called is if you are using polymorphism and have not made your base destructors virtual.

DanDan
In this case, you will get undefined behaviour.
anon
+12  A: 

Are there any other circumstances where they[destructors] will not be called?

  1. Long jumps: these interfere with the natural stack unwinding process and often lead to undefined behavior in C++.
  2. Premature exits (you already pointed these out, though it's worth noting that throwing while already stack unwinding as a result of an exception being thrown leads to premature exits and this is why we should never throw out of dtors)
  3. Throwing from a constructor does not invoke the dtor for a class. This is why, if you allocate multiple memory blocks with managed by several different pointers (and not smart pointers), you need to use function-level try blocks or avoid using the initializer list and have a try/catch block in the ctor body (or better yet, just use a smart pointer like scoped_ptr since any member successfully initialized so far in an initializer list will be destroyed even though the class dtor will not be called).
  4. As pointed out, failing to make a dtor virtual when a class is deleted through a base pointer could fail to invoke the subclass dtors (undefined behavior).
  5. Failing to call matching operator delete/delete[] for an operator new/new[] call (undefined behavior - may fail to invoke dtor).
  6. Failing to manually invoke the dtor when using placement new with a custom memory allocator in the deallocate section.
  7. Using functions like memcpy which only copies one memory block to another without invoking copy ctors.
Nice list, but there is a flaw in point 3: you can actually put a try block around the initializer list, look up function level try blocks: `struct X { X() try : x_(42) {} catch (...) {} private: int x_; };` Actually you can use this for any function like `void foo() try {} catch (...) {}` but some important compilers (VS2008, don't know if this is fixed in later versions) choke on it. The advice about smart pointers remains valid though.
Fabio Fracassi
@Fabio Thanks Fabio, I'll point that out! I wasn't aware of these function-level try/catch blocks (prefer to just use RAII everywhere as it relieves headaches).
In general, a nice list. However, #4, and #5 are technically undefined behavior, so the standard has nothing to say about whether destructors get called or not. Most compilers will behave as you say (or may just crash).
KeithB
The function `try` is illustrated in the GOTW #66 (just pointed it out to a colleague today... coincidences...)
Matthieu M.
@KeithB Ah yes, I modified the answer to specify that these are undefined behaviors: we shouldn't expect anything sane to happen.
+2  A: 

If a function or method has a throws specification, and throws something NOT covered by the specification, the default behavior is to exit immediately. The stack is not unwound and destructors are not called.

POSIX signals are an operating system specific construct and have no notion of C++ object scope. Generally you can't do anything with a signal except maybe, trap it, set a global flag variable, and then handle it later on in your C++ code after the signal handler exits.

Recent versions of GCC allow you to throw an exception from within synchronous signal handlers, which does result in the expected unwinding and destruction process. This is very operating system and compiler specific, though

karunski