Why does `std :: make_shared` make two separate allocations with` -fno-rtti`?

advertisements
#include <memory>
struct foo { };
int main() { std::make_shared<foo>(); }

The asssembly generated by both g++7 and clang++5 with -fno-exceptions -Ofast for the code above:

  • Contains a single call to operator new if -fno-rtti is not passed.

  • Contains two separate calls to operator new if -fno-rtti is passed.

This can be easily verified on gcc.godbolt.org (clang++5 version):

Why is this happening? Why does disabling RTTI prevent make_shared from unifying the object and control block allocations?


No good reason. This looks like a QoI issue in libstdc++.

Using clang 4.0, libc++ does not have this issue., while libstdc++ does.

The libstdc++ implementation with RTTI relies on get_deleter:

void* __p = _M_refcount._M_get_deleter(typeid(__tag));
                  _M_ptr = static_cast<_Tp*>(__p);
                  __enable_shared_from_this_helper(_M_refcount, _M_ptr, _M_ptr);
_M_ptr = static_cast<_Tp*>(__p);

and in general, get_deleter isn't possible to implement without RTTI.

It appears that it is using the deleters position and the tag to store the T in this implementation.

Basically, the RTTI version used get_deleter. get_deleter relied on RTTI. Getting make_shared to work without RTTI required rewriting it, and they took an easy route that caused it to do two allocations.

make_shared unifies the T and reference counting blocks. I suppose with both variable sized deleters and variable sized T things get nasty, so they reused the deleter's variable sized block to store the T.

A modified (internal) get_deleter that did not do RTTI and returned a void* might be enough to do what they need from this deleter; but possibly not.