Почему `std:: make_shared` выполняет два отдельных распределения с` -fno-rtti`?
#include <memory>
struct foo { };
int main() { std::make_shared<foo>(); }
Asssembly, сгенерированный как g++7
, так и clang++5
с -fno-exceptions -Ofast
для кода выше:
-
Содержит один вызов operator new
, если -fno-rtti
прошел не.
-
Содержит два отдельных вызова до operator new
, если -fno-rtti
передано.
Это легко проверить на gcc.godbolt.org (clang++5
версия):
![снимок экрана вышеуказанной ссылки godbolt с новыми вызовами оператора с высоким уровнем обслуживания></a></p> <p>Почему это происходит? Почему отключение RTTI предотвращает <code>make_shared</code> от объединения выделения объекта и блока управления?</P></div></body></html>]()
Ответы
Ответ 1
Нет веской причины. Это похоже на проблему QoI в libstdС++.
Использование clang 4.0, libС++ не имеет этой проблемы., а libstdС++ делает.
Реализация libstdС++ с RTTI опирается на 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_deleter
. get_deleter
полагался на RTTI. Получение make_shared
для работы без RTTI
потребовало перезаписать его, и они сделали простой путь, из-за которого он выполнял два распределения.
make_shared
объединяет блоки T
и подсчета ссылок. Я полагаю, что как с переменными размерами, так и с переменными размерами T
все становится неприятно, поэтому они повторно использовали блок размера переменной для удаления, чтобы сохранить T
.
Измененный (внутренний) get_deleter
, который не выполнял RTTI, и возвратил void*
, может быть достаточно, чтобы делать то, что им нужно от этого deleter; но, возможно, нет.
Ответ 2
Почему отключение RTTI предотвращает объединение make_shared объекта и распределения блоков управления?
Вы можете видеть на ассемблере (просто вставка текста действительно предпочтительнее как для ссылок, так и для его съемки), что унифицированная версия не выделяет простой foo
, а std::_Sp_counted_ptr_inplace
, а далее, что type имеет 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, он не сможет сгенерировать указатель подсчета inplace, поскольку он должен быть виртуальным.
Обратите внимание, что версия, отличная от inplace, по-прежнему относится к vtable, но, по-видимому, она просто хранит адрес де виртуализированного деструктора.
Ответ 3
Естественно, std::shared_ptr
будет реализовано с предположением о компиляторе, поддерживающем rtti
. Но он может быть реализован без него. См. shared_ptr без RTTI?.
Взять реплику из этой старой ошибки GCC libstdС++ # 42019. Мы можем видеть, что Джонатан Вакели добавил исправление, чтобы сделать это возможным без RTTI.
В GCC libstdС++ std::make_shared
использует службы std::allocated_shared
, который использует нестандартный конструктор (как видно из кода, воспроизводится ниже).
Как видно из паттерна из строки 753, вы можете видеть, что получение дефолта по умолчанию просто требует использования служб typeid
, если RTTI включен, в противном случае для него требуется отдельное выделение, которое не зависит от RTTI.
РЕДАКТИРОВАТЬ: 9 - май -2017: удалено авторский код, ранее размещенный здесь
Я не исследовал libcxx
, но я хочу верить, что они сделали схожие вещи....