46

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?

WilliamKF
  • 36,283
  • 61
  • 170
  • 271
  • 12
    As pointed out here, http://thedailywtf.com/Articles/My-Tales.aspx, you should also be aware the destructor will not be called when the power-plug is pulled ;). – Björn Pollex Jul 05 '10 at 13:00
  • 4
    SIGINT will not unwind the stack unless you install a signal handler that overrides the default behavior. By default SIGINT results in immediate program termination. – karunski Jul 05 '10 at 14:05
  • Not posting this as an answer because it seems more like an oversight in the question. Destructors are only automatically called (in normal circumstances) at the end of the lifetime of objects with static, automatic or thread storage duration. For object with **dynamic** storage duration the destructor is only called when `delete` is called on a pointer to the object. So a destructor is not going to be called for dynamic objects for which `delete` is never called (whether because a memory leak makes this impossible, or by oversight). – Marc van Leeuwen Apr 18 '15 at 11:08

8 Answers8

49

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 undefined behavior 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 managed by several different pointers (and not smart pointers) in a ctor, 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. mem* functions are deadly in C++ as they bulldoze over the private data of a class, overwrite vtables, etc. The result is typically undefined behavior.
  8. Instantiation of some of smart pointers (auto_ptr) on an incomplete type, see this discussion
Community
  • 1
  • 1
stinky472
  • 6,459
  • 25
  • 27
  • 4
    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 Jul 05 '10 at 14:32
  • @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). – stinky472 Jul 05 '10 at 14:34
  • 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 Jul 05 '10 at 15:32
  • 1
    The function `try` is illustrated in the GOTW #66 (just pointed it out to a colleague today... coincidences...) – Matthieu M. Jul 05 '10 at 16:42
  • @KeithB Ah yes, I modified the answer to specify that these are undefined behaviors: we shouldn't expect anything sane to happen. – stinky472 Jul 07 '10 at 23:54
  • 1
    Thanks for the tip on function level try blocks, @FabioFracassi. I can't believe I've been doing C++ for 15 years and either forgot about or never encountered that feature. – Trebor Rude Apr 22 '13 at 20:26
  • stinky472 and @KeithB: You are wrong about #5: [memory leaks are not undefined behavior](http://stackoverflow.com/a/1978859/1436796) – Marc van Leeuwen Apr 18 '15 at 11:18
  • Technically case #3 is a non-case, since the object for which the destructor is not called never even exists in the first place(though all "its" members do). Also I would have listed the case of objects that are variants of a `union`, and that do not get their destructors implicitly called when the lifetime of the union ends (though if care is taken a user-provided destructor of the union might do this). But maybe this falls under case #6 (I don't understand what you say about custom allocators here) since such variants will usually have to be created by placement-new. – Marc van Leeuwen Apr 18 '15 at 11:34
  • @MarcvanLeeuwen You are correct that memory leaks are not undefined behavior. However calling delete on memory that was allocated with new[] is undefined. See the answer to this question [How serious is the new/delete operator mismatch error?](http://stackoverflow.com/questions/9238731/how-serious-is-the-new-delete-operator-mismatch-error) – KeithB Apr 20 '15 at 13:57
  • @KeithB: Yes, and calling `delete` on a pointer that was obtained from `malloc`, or that points into non-dynamic memory in the first place is probably also UB. I don't see how that is related to my comment, or to this answer, though. – Marc van Leeuwen Apr 20 '15 at 14:06
  • MSVC with OMP directives [does not guarantee the destruction of `threadprivate` objects.](http://stackoverflow.com/a/32383184/1858225) Does this fall under any of your list items, or is it separate? Is the MSVC behavior even conformant here? – Kyle Strand Sep 03 '15 at 19:52
  • I don't believe throwing while unwinding because of another exception is undefined. (point 2). It's defined to call std::terminate – Arvid Nov 06 '16 at 22:37
  • @Arvid is correct; throwing while unwinding is **not** undefined behavior. See here: http://en.cppreference.com/w/cpp/error/terminate. – Craig M. Brandenburg Feb 15 '17 at 00:43
7

The C++ standard says nothing about how specific signals must be handled - many implementations may not support SIGINT, etc. Destructors will 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.

Craig M. Brandenburg
  • 2,624
  • 2
  • 22
  • 33
  • 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 Jul 05 '10 at 14:02
  • 1
    @karunski It certainly exits the scope if a signal handler is installed. –  Jul 05 '10 at 14:08
  • @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 Jul 05 '10 at 14:13
  • @karunski Do you have a Standard reference for the behaviour when a handler is not installed? –  Jul 05 '10 at 14:15
  • 1
    @Neil Butterworth Here's the POSIX standard signal handlers: http://opengroup.org/onlinepubs/007908775/xsh/signal.h.html – karunski Jul 05 '10 at 14:19
  • 1
    @karunski POSIX does not define the C++ language. –  Jul 05 '10 at 14:23
  • @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 Jul 05 '10 at 14:36
  • 1
    @karunski But the question is about C++. –  Jul 05 '10 at 14:37
3

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
  • 75,980
  • 39
  • 193
  • 271
3

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

DanDan
  • 10,037
  • 8
  • 49
  • 68
2

A lot of answers here but still incomplete!

I found another case where destructors are not executed. This happens always when the exception is catched across a library boundary.

See more details here:

Destructors not executed (no stack unwinding) when exception is thrown

Community
  • 1
  • 1
Elmue
  • 5,679
  • 41
  • 53
  • This seems like a bug in that I doubt the C++ specification allows for such behavior. – WilliamKF Jan 25 '14 at 17:13
  • The question here is: "Under what circumstances destructors are not called". Even if this is a bug in Visual Studio, it is a valid answer to the question, because a bug is also a cirumstance. People come here from Google and some of them may experience just this specific problem without knowing if it is bug or not. – Elmue Feb 01 '14 at 02:25
2

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
  • 89,955
  • 22
  • 127
  • 208
2

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
  • 3,820
  • 2
  • 15
  • 10
1

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,304
  • 9
  • 13