C++ 为什么“std::make_shared”使用“-fno rtti”执行两个单独的分配? #包括 结构foo{}; int main(){std::make_shared();}

C++ 为什么“std::make_shared”使用“-fno rtti”执行两个单独的分配? #包括 结构foo{}; int main(){std::make_shared();},c++,c++11,rtti,C++,C++11,Rtti,对于上述代码,g++7和clang++5与-fno exceptions-Ofast生成的组合: 如果-fno-rtti未通过,则包含对操作员new的单个调用 如果-fno-rtti被通过,则包含对新操作员的两个独立调用 这很容易验证(): 为什么会这样?为什么禁用RTTI会阻止make_shared统一对象和控制块分配 为什么禁用RTTI会阻止make_shared统一对象和控制块分配 您可以从汇编程序中看到(仅粘贴文本比链接和拍照更可取),统一版本不分配简单的foo,而是分配一个std

对于上述代码,
g++7
clang++5
-fno exceptions-Ofast
生成的组合:

  • 如果
    -fno-rtti
    未通过,则包含对
    操作员new
    的单个调用

  • 如果
    -fno-rtti
    通过,则包含对
    新操作员的两个独立调用

这很容易验证():

为什么会这样?为什么禁用RTTI会阻止
make_shared
统一对象和控制块分配

为什么禁用RTTI会阻止make_shared统一对象和控制块分配


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

mov QWORD PTR[rax],偏移平坦:
适于
标准::_Sp_counted_ptr_in place+16
如果禁用RTTI,它将无法生成就地计数指针,因为它需要是虚拟的


请注意,非就地版本仍然是指vtable,但它似乎只是直接存储非虚拟化析构函数地址。

当然,
std::shared_ptr
将在假设编译器支持
rtti
的情况下实现。但它可以在没有它的情况下实现。请参阅

从这个旧的GCC的libstdc++中得到一个提示。我们可以看到,它添加了一个补丁,使这在没有RTTI的情况下成为可能

在GCC的libstdc++中,它使用了一个非标准构造函数(如代码所示,如下所示)

如图所示,您可以看到,如果启用了RTTI,获取默认删除程序只需使用
typeid
服务,否则,它需要不依赖RTTI的单独分配

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


我还没有调查过,但我想相信他们做了类似的事情……

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

使用Clang4.0,同时

带有RTTI的libstdc++实现依赖于
get\u deleter

mov QWORD PTR [rax], OFFSET FLAT:
  vtable for
  std::_Sp_counted_ptr_inplace<foo, std::allocator<foo>,
  (__gnu_cxx::_Lock_policy)2>+16
void*\u p=\u M\u refcount.\u M\u get\u deleter(typeid(\u标记));
_M_ptr=静态铸造(p);
__从\u此\u帮助程序启用\u共享\u(\u M\u refcount、\u M\u ptr、\u M\u ptr);
_M_ptr=静态铸造(p);
一般来说,如果没有RTTI,
get\u deleter
是不可能实现的

在这个实现中,它似乎正在使用deleters位置和标记来存储
T

基本上,使用的RTTI版本
get\u deleter
get\u deleter
依赖于RTTI。要使
make\u shared
在不使用
RTTI
的情况下工作,需要重写它,并且他们采取了一种简单的方法,使它执行两次分配

make_shared
T
和引用计数块统一起来。我想对于可变大小的删除器和可变大小的
T
来说,事情变得很糟糕,所以他们重用了删除器的可变大小块来存储
T


修改后的(内部)
get\u deleter
不执行RTTI并返回
void*
可能足以从该删除器执行所需操作;但可能无法执行。

相关:由于禁用了虚拟函数,库无法使用压缩结构(元素、引用计数和删除器)因为它需要类型擦除。因此库需要分配元素+ ReFeCube和DELTER分隔。这也是“没有RTTI+无异常产生最快C++代码”的一个很好的例子。这是一些开发人员坚持思考的问题。下面的示例证明rtti实际上可以生成更好的代码。没有禁用任何虚拟函数;这是代表libstdc++的QoI故障。libc++在这两种情况下都进行了优化实现。您是否使用
std::shared_ptr(new foo())
获得3次分配?不,只有2.Hmm。“仅仅粘贴文本确实比链接和拍照更可取-文本可以在godbolt中找到,它链接在快照的正上方。在我看来,在OP中粘贴生成的程序集是个坏主意,因为必须单击屏幕截图或链接比必须滚动浏览大量程序集IMHO要好得多。此外,这是否应被视为QoI(“实现质量”))问题?是的,文本现在可以在godbolt上找到。但是谁知道当这个网站还在运行时链接是否会断开?是的,我认为这是一个QoI实现,从某种意义上说,实现者编写了一个完全没有rtti的第二个版本是非常慷慨的。我不会责怪他们仅仅说没有rtti共享ptr是不可实现的。(回想一下,它通常需要一个虚拟析构函数来处理定制的删除程序“--使用
make_shared
无法自定义删除程序?如果您使用libc++,即使禁用rtti,它也是一个单一的分配。您可以使用
-fno rtti
执行任何操作,而不是使用
dynamic_cast
typeid
。不,
shared_ptr
在删除程序上没有参数化(模板)。实例确实有一个删除程序,但是
make_shared
必须创建自己的删除程序来销毁
T
,而不解除分配它。使用
make_shared
您知道您要销毁的确切类型。需要RTTI的销毁程序是荒谬的。这是受版权保护的代码,因此发布它违反了服务条款
mov QWORD PTR [rax], OFFSET FLAT:
  vtable for
  std::_Sp_counted_ptr_inplace<foo, std::allocator<foo>,
  (__gnu_cxx::_Lock_policy)2>+16
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);