为什么 `std::make_shared` 使用 `-fno-rtti` 执行两个单独的分配?

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

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

g++7clang++5-fno-exceptions -Ofast 为上述代码生成的程序集:

这个很容易验证on gcc.godbolt.org (clang++5 version):

为什么会这样?为什么禁用 RTTI 会阻止 make_shared 统一 objectcontrol block 分配?

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

你可以从汇编程序中看到(只粘贴文本确实比链接和拍照更可取)统一版本不分配简单的 foo 而是分配 std::_Sp_counted_ptr_inplace,而且该类型有一个 vtable(回想一下,它通常需要一个虚拟析构函数,以处理自定义删除器)

mov QWORD PTR [rax], OFFSET FLAT:
  vtable for
  std::_Sp_counted_ptr_inplace<foo, std::allocator<foo>,
  (__gnu_cxx::_Lock_policy)2>+16

如果禁用 RTTI,它无法生成就地计数指针,因为它需要是虚拟的。

注意non-inplace版本仍然引用了一个vtable,但它似乎只是直接存储去虚拟化的析构函数地址。

当然,std::shared_ptr会在编译器支持rtti的前提下实现。但它可以在没有它的情况下实施。参见

从这个旧的 GCC 的 libstdc++ 中得到启发 #42019 bug. We can see that Jonathan Wakely 添加了一个修复程序,使这在没有 RTTI 的情况下成为可能。

在 GCC 的 libstdc++ 中,std::make_shared uses the services of std::allocated_shared 使用了非标准构造函数(如代码所示,转载如下)。

如本 patch, from line 753 所示,您可以看到获取默认删除器只需要使用 typeid 如果启用了 RTTI 的服务,否则, 它需要不依赖于 RTTI 的单独分配

编辑: 9 - 2017 年 5 月:删除了之前在此处发布的受版权保护的代码

我没有调查过 libcxx,但我想相信他们做过类似的事情....

没有充分的理由。这看起来像是 libstdc++ 中的 QoI 问题。

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

带有 RTTI 的 libstdc++ 实现依赖于 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);

一般来说,get_deleter 没有 RTTI 是不可能实现的。

它似乎在这个实现中使用删除位置和标签来存储T

基本上使用的RTTI版本get_deleterget_deleter 依赖 RTTI。让 make_shared 在没有 RTTI 的情况下工作需要重写它,他们采用了一种简单的方法,导致它进行两次分配。

make_shared 统一了 T 和引用计数块。我想对于可变大小的删除器和可变大小的 T 事情变得令人讨厌,所以他们重新使用删除器的可变大小块来存储 T.

修改后的(内部)get_deleter 不执行 RTTI 并返回 void* 可能足以完成他们需要的删除程序;但可能不会。