A word is due on vector deleting destructors – previously mentioned as the only functions that got weakly bound by the linker. The usual disclaimers apply: everything that follows is my own investigation, in code and online. Nothing here is official in any way, and even if I did get something right – it is subject for change at any time.
While C’s malloc/free deal purely with memory management, C++’s new/delete do more: they construct/destruct the objects being allocated/deallocated. (Preemptive nitpick: there are other differences, they are not the subject of this post). There is a small family of compiler generated functions that help achieve these additional tasks: vector constructor, scalar deleting destructor, vector deleting destructor, and vector ctor/dtor iterators.
The following toy code will be used to illustrate:
struct Whatever { Whatever() {}; ~Whatever() {}; }; int main(int argc, char* argv[]) { Whatever* pW = new Whatever; delete pW; Whatever* arrW = new Whatever[10]; delete[] arrW; return 0; }
new
when ‘new Whatever’ is executed, two things happen:
1) Memory is allocated, by a call to operator-new (which unless overridden, is essentially a wrapper around plain old malloc),
2) Whatever’s constructor is called.
Proof by a glimpse into unoptimized disassembly:
delete
When ‘single’ delete is called on a Whatever pointer, the opposite happens in reverse order: first Whatever’s destructor is called, then operator-delete (which by default is equivalent to ‘free’) frees the unpopulated memory. In this case, however, the compiler does not call ~Whatever() and operator-delete directly, but rather generates and invokes a helper function that wraps these two calls. This helper is called scalar deleting destructor – which makes sense, since it destructs and deletes. Some more disassembly screenshots:
Why is the new+construction inlined and the delete+destruction wrapped in a helper? Beats me. I would have thought that the exact same inlining tradeoff (binary size vs. call overhead) applies for both cases.
new[] / delete[]
When the vector versions new[] and delete[] are called, an additional layer is added, to address the need to iterate over the Whatever object slots, and construct/destruct them one at a time.
Enter ‘vector constructor iterator’ and ‘vector destructor iterator’. In detail:
1) a new[] statement translates into a call to an operator-new with size enough to hold all Whatever’s, then a call to ‘eh vector constructor iterator’ which is essentially a for-loop of Whatever::Whatever()’s in the designated array locations.
2) A delete[] statement translates into a single call to vector deleting destructor, which in turn calls ‘eh vector destructor iterator’ and then operator delete.
Being merciful, I won’t hurt your eyes with more disassembly. Just believe me or go dig in yourselves.
Other findings, in no particular order
1) the ‘eh’ prefix in the vector ctor/dtor iterators stands for exception handling. If you compile with no c++ exceptions, a non-eh version of the iterators is emitted. (This has nothing to do with std::nothrow, which controls the behaviour of operator new – a different stage of the object creation.)
2) The deleting destructors, both scalar and vector, are generated as hidden methods of the type Whatever. All other helper functions (vector constructor, ctor/dtor iterators) are not. Not sure why, but I suspect it has to do with a supposed need for weak linkage – more on that in a future post.
3) The compiler is smart enough to avoid generating and invoking unneeded helper functions. For example, comment out the coded ctor Whatever::Whatever(), and watch as the vector constructor call vanishes.
4) The vector deleting destructor is unique in that it has some built in flexibility. Raymond Chen spelled out pseudo code for it, which I shall shamelessly paste now:
void Whatever::vector deleting destructor(int flags) { if (flags & 2) { // if vector destruct size_t* a = reinterpret_cast<size_t*>(this) - 1; size_t howmany = *a; vector destructor iterator(p, sizeof(Whatever), howmany, Whatever::~Whatever); if (flags & 1) { // if delete too operator delete(a); } } else { // else scalar destruct this->~Whatever(); // destruct one if (flags & 1) { // if delete too operator delete(this); } } }
So from the vector deleting dtor’s viewpoint, memory deallocation is optional and the same function can serve as a scalar deleting dtor (when flags & 2 ==0) . In practice I have never seen a vector deleting destructor called with ‘flags’ different from 3 (i.e., vector, deleting destructor). I can come up with somewhat contrived scenarios where this flexibility might be useful – say, a memory manager that wants to destroy objects but keep the memory for faster future usage. However, deleting dtors are accessible only to the compiler anyway, so the purpose of this flexibility is not clear to me. Insights are very welcome.
If you have a really huge array (and huge machine to handle it), watch out for this bug in the vector new/delete:
https://connect.microsoft.com/VisualStudio/feedback/details/802400/constructors-do-not-run-for-objects-in-arrays-with-more-than-max-int-elements
Thanks – I never allocated such a huge array, but will try to keep this in mind. Most probably the culprit is the vector ctor counter which was coded to int instead of size_t.
I am still wondering why there is a `scalar deleting destructor’ since `vector deleting destructor’ can do its job (just passing flags with bit 1 set)?
Since this is an unoptimized build, I guess there is no function called `scalar vector construtor’, so the assembly is generated inline.
From what I can tell, the `eh vector contructor iterator` is only used if the class in question has a non-trivial constructor, even if exception handling is enabled. Even if the constructor uses a function-try-block, it’ll still use the normal `vector constructor iterator` if the destructor is trivial.
Raymond Chen’s blog post has moved here: https://devblogs.microsoft.com/oldnewthing/20040203-00/?p=40763 (linked from here: https://devblogs.microsoft.com/oldnewthing/2004/02/03)