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

Montaż generowany przez zarówno g++7 i clang++5 za pomocą -fno-exceptions -Ofast dla powyższego kodu:

  • Zawiera pojedyncze połączenie do operator new, jeśli -fno-rtti jest , a nie minęło.

  • Zawiera dwa oddzielne połączenia do operator new, jeśli -fno-rtti jest przeszedł .

Można to łatwo zweryfikować na gcc.godbolt.org (< href = "https://godbolt.org/g/wikzod" rel = "NefErrer"> clang++5 wersja ) :

screenshot of the above godbolt link with highlighed operator new calls

Dlaczego to się dzieje? Dlaczego wyłączanie RTTI uniemożliwia make_shared z ujednolicenia blokady obiekt i alokacje ?

25
Vittorio Romeo 29 marzec 2017, 15:26

3 odpowiedzi

Najlepsza odpowiedź

Nie ma dobrego powodu. Wygląda to jak problem z Qoi w Libstdc ++.

Korzystanie z Clang 4.0, libc ++ nie ma tego problemu., podczas gdy libstdc ++ robi.

Implementacja libstdc ++ z RTTI opiera się na 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);

W ogóle get_deleter nie można wdrożyć bez RTTI.

Wygląda na to, że korzysta z pozycji Usuwarki i tag, aby zapisać T w tej implementacji.

Zasadniczo używana wersja RTTI get_deleter. get_deleter Opierał się na RTTI. Dostanie make_shared do pracy bez RTTI wymagane przepisanie go i wzięli łatnakę, która spowodowała, że wykonuje dwa przydziały.

make_shared Unika bloków liczenia T i odniesienia. Przypuszczam, że zarówno z zmiennych kasetami i zmienną wielkości T rzeczy są paskudne, więc ponownie wykorzystane blok o zmiennej Deletera do przechowywania T.

Zmodyfikowany (wewnętrzny) {x0}} który nie zrobił RTTI i zwrócił void* może wystarczyć, aby zrobić to, czego potrzebują z tego deletera; ale prawdopodobnie nie.

6
Yakk - Adam Nevraumont 30 marzec 2017, 15:52

Dlaczego wyłączanie RTTI zapobiega ujednoliceniu alokacji obiektu i bloków kontrolnych?

Możesz zobaczyć z asemblera (właśnie wklejając tekst jest naprawdę korzystny zarówno do łączenia, jak i do robienia zdjęć), że ujednolicona wersja nie przydzienia prostego foo, ale std::_Sp_counted_ptr_inplace i dalej i dalej Typ ten ma VTable (przypomnij sobie, że potrzebuje wirtualnego destruktora w ogóle, aby poradzić sobie z celownikami niestandardowymi)

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

Jeśli wyłączysz RTTI, nie może generować wskaźnika wskaźnika, ponieważ to musi być wirtualne.

Należy pamiętać, że wersja bez inwale wciąż odnosi się do VTABLE, ale wydaje się, że wystarczy przechowywać bezpośredni bezpośredni adres destructora.

12
Useless 29 marzec 2017, 13:33

Naturalnie, std::shared_ptr zostanie wdrożona przy założeniu kompilatora wspierającego rtti. Ale może być wdrażany bez niego. Zobacz Sharered_ptr bez RTTI?.

Biorąc wskazówki od tego starego gcc's libstdc ++ # 42019 Błąd. Widzimy, że Jonathan Męski dodał poprawkę, aby to możliwe bez RTTI.

W GCC's Libstdc ++, {X0}} wykorzystuje usługi std::allocated_shared, który wykorzystuje niestandardowy konstruktor (jak widać w kodzie, odtwarzany poniżej).

Jak widać w tym Patch, z linii 753, można zobaczyć, że pobieranie domyślnego deletera wymaga po prostu korzystanie z usług typeid , jeśli RTTI jest włączony , w przeciwnym razie , wymaga oddzielnej alokacji , która nie zależy od RTTI.

Edytuj: 9 - maj -2017: Usunięty kod autorski został wcześniej opublikowany tutaj

Nie zbadałem libcxx, ale Chcę wierzyć, że zrobili podobną rzecz ....

11
Community 23 maj 2017, 12:17