Подтвердить что ты не робот

Почему `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>

4b9b3361

Ответ 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, но я хочу верить, что они сделали схожие вещи....