Czy adres C ++ 11 dotyczy przekazywania obiektów std lib między granicami biblioteki dynamicznej / współdzielonej? (tj. dll i tak dalej)?

34

Jedną z moich głównych skarg na C ++ jest to, jak trudno w praktyce przekraczać obiekty biblioteki standardowej poza granicami biblioteki dynamicznej (tj. Dll / so).

Biblioteka std jest często tylko nagłówkiem. Co jest świetne do robienia niesamowitych optymalizacji. Jednak w przypadku bibliotek DLL często są one budowane z różnymi ustawieniami kompilatora, które mogą wpływać na wewnętrzną strukturę / kod kontenerów biblioteki standardowej. Na przykład w MSVC jedna biblioteka DLL może się kompilować z włączonym debugowaniem iteratora, podczas gdy inna kompilacja jest wyłączona. Te dwie biblioteki dll mogą napotykać problemy z przekazywaniem standardowych pojemników. Jeśli ujawniam się std::stringw interfejsie, nie mogę zagwarantować, że kod, którego używa klient, jest std::stringdokładnie zgodny z kodem mojej biblioteki std::string.

Prowadzi to do trudnych problemów z debugowaniem, bólów głowy itp. Albo sztywno kontrolujesz ustawienia kompilatora w swojej organizacji, aby zapobiec tym problemom, albo używasz prostszego interfejsu C, który nie będzie miał takich problemów. Lub określ swoim klientom oczekiwane ustawienia kompilatora, których powinni użyć (co jest do bani, jeśli inna biblioteka określa inne ustawienia kompilatora).

Moje pytanie brzmi, czy C ++ 11 próbował zrobić coś, aby rozwiązać te problemy?

Doug T.
źródło
3
Nie znam odpowiedzi na twoje pytanie, ale mogę powiedzieć, że masz obawy; są kluczem do tego, dlaczego nie będę używać C ++ w moich projektach, ponieważ cenimy stabilność ABI zamiast wyciskania każdego ostatniego cyklu potencjalnej wydajności.
Donal Fellows
2
Proszę rozróżnić. Trudno między DLLs. Pomiędzy SOs zawsze działało dobrze.
Jan Hudec
1
Ściśle mówiąc, nie jest to problem wyłącznie w języku C ++. Problem może występować w przypadku innych języków.
MrFox,
2
@ JanHudec Mogę zagwarantować, że między SO nie działa tak magicznie, jak się wydaje. Biorąc pod uwagę widoczność symboli i jak często działa zmiana nazw, możesz być bardziej izolowany od problemu, ale kompilowanie jednego .so z różnymi flagami / itp., I zakładanie, że możesz połączyć go w programie z innymi flagami, jest receptą na katastrofę.
sdg
3
@sdg: Przy domyślnych flagach i domyślnej widoczności działa. Jeśli je zmienisz i wpakujesz się w kłopoty, to twój problem i nikt inny.
Jan Hudec

Odpowiedzi:

20

Masz rację, że czegokolwiek STL - a właściwie wszystkiego z jakiejkolwiek biblioteki stron trzecich, która jest szablonowana - najlepiej unikać w każdym publicznym API C ++. Chcesz również przestrzegać długiej listy reguł na stronie http://www.ros.org/reps/rep-0009.html#definition, aby zahamować uszkodzenie ABI, co sprawia, że ​​programowanie publicznych interfejsów API C ++ jest obowiązkowe.

Odpowiedź dotycząca C ++ 11 brzmi: nie, ten standard tego nie dotyka. Bardziej interesujące jest dlaczego nie? Odpowiedź brzmi, ponieważ C ++ 17 bardzo go dotyka, a do wdrożenia modułów C ++ potrzebujemy wyeksportowanych szablonów do działania, a do tego potrzebujemy kompilatora typu LLVM, takiego jak clang, który może zrzucić całą AST na dysk, a następnie wykonuj wyszukiwania zależne od dzwoniącego, aby obsłużyć wiele przypadków naruszających ODR w każdym dużym projekcie C ++ - który, nawiasem mówiąc, zawiera wiele kodu GCC i ELF.

Na koniec widzę wiele komentarzy nienawiści do MSVC i pro-GCC. Są one bardzo źle poinformowane - GCC na ELF jest zasadniczo i nieodwracalnie niezdolne do wytworzenia prawidłowego i poprawnego kodu C ++. Powodów jest wiele i legion, ale szybko przytoczę jeden przykład: GCC na ELF nie może bezpiecznie tworzyć rozszerzeń Pythona napisanych przy użyciu Boost.Python, w których więcej niż jedno rozszerzenie oparte na Boost.Python jest ładowane do Pythona. Wynika to z faktu, że ELF ze swoją globalną tablicą symboli C jest po prostu niezdolny do zaprojektowania, aby zapobiec naruszeniom ODR powodującym awarie segregacji, podczas gdy PE i MachO, a nawet proponowana specyfikacja modułów C ++ wszystkie używają tabel symboli dla poszczególnych modułów - co przy okazji oznacza również znacznie szybsze czasy inicjowania procesu. I jest o wiele więcej problemów: zobacz StackOverflow, na który ostatnio odpowiedziałemhttps://stackoverflow.com/questions/14268736/symbol-visibility-exceptions-runtime-error/14364055#14364055 na przykład, gdy przypadki wyjątków C ++ są nieodwracalnie fundamentalnie uszkodzone na ELF.

Ostatni punkt: w odniesieniu do interakcji różnych STL jest to wielki problem dla wielu dużych użytkowników korporacyjnych próbujących łączyć biblioteki stron trzecich, które są ściśle zintegrowane z niektórymi implementacjami STL. Jedynym rozwiązaniem jest nowy mechanizm dla C ++ do obsługi interpolacji STL, a gdy już są dostępne, równie dobrze możesz naprawić interpolację kompilatora, abyś mógł (na przykład) mieszać MSVC, GCC i clang skompilować pliki obiektów i wszystko po prostu działa . Obejrzałbym wysiłek C ++ 17 i zobaczyłem, co się tam pojawi w ciągu najbliższych kilku lat - byłbym zaskoczony, gdyby nic nie zrobiło.

Niall Douglas
źródło
Świetna odpowiedź! Mam tylko nadzieję, że Clang poprawi kompatybilność z Windows i może ustawić dobry domyślny kompilator standardowy. Tekstowy system włączania / nagłówka C ++ jest okropny, nie mogę się doczekać dnia, kiedy moduły uprościły organizację kodu C ++, nieskończenie przyspieszają czasy kompilacji i poprawiają interoperacyjność kompilatora z połowami naruszającymi ODR.
Alessandro Stamatto,
3
Osobiście spodziewam się znacznego wzrostu czasu kompilatora. Szybkie przemierzanie wewnątrz-modułu AST jest bardzo trudne i prawdopodobnie będziemy potrzebować jego pamięci podręcznej w pamięci współdzielonej. Jednak prawie wszystko, co złe, staje się lepsze. BTW, pliki nagłówkowe zdecydowanie pozostają w pobliżu, obecne moduły C ++ mają mapy interfejsów mapowane od 1 do 1 do plików nagłówkowych. Ponadto automatycznie generowane pliki interfejsów będą zgodne z C ++, więc starszy nagłówek po prostu odfiltrowuje makra C i wypluwa jako pliki interfejsu. Fajnie co?
Niall Douglas,
Fajne! Mam tyle wątpliwości co do modułów. Czy system modułowy uwzględni włączenie tekstowe a włączenie symboliczne? Dzięki obecnej dyrektywie dołączającej kompilator musi rekompilować dziesiątki tysięcy wierszy kodu w kółko dla każdego pliku źródłowego. Czy system modułów pozwoli kiedyś na kod bez deklaracji przesyłania dalej? Czy poprawi / ułatwi budowanie narzędzi?
Alessandro Stamatto,
2
-1 za sugerowanie, że wszystkie szablony innych firm są podejrzane. Zmiana konfiguracji jest niezależna od tego, czy konfigurowana rzecz jest szablonem.
DeadMG
1
@Alessandro: proponowane moduły C ++ jawnie wyłączają makra C. Możesz użyć szablonów lub nowt. Proponowane interfejsy są zgodne z prawem C ++, jedynie generowane automatycznie, i mogą być opcjonalnie wstępnie skompilowane w celu przyspieszenia analizy, tzn. Nie należy oczekiwać przyspieszenia w stosunku do istniejących wstępnie skompilowanych nagłówków. Ostatnie dwa pytania, właściwie nie wiem: to zależy :)
Niall Douglas
8

Specyfikacja nigdy nie miała tego problemu. Jest tak, ponieważ ma koncepcję zwaną „regułą jednej definicji”, która nakazuje, aby każdy symbol miał dokładnie jedną definicję w uruchomionym procesie.

Biblioteki DLL systemu Windows naruszają ten wymóg. Dlatego są te wszystkie problemy. Więc to Microsoft musi to naprawić, a nie komitet normalizacyjny C ++. Unix nigdy nie miał tego problemu, ponieważ biblioteki współdzielone działają tam inaczej i domyślnie są zgodne z jedną regułą definicji (możesz to jawnie złamać, ale oczywiście robisz to tylko wtedy, gdy wiesz, że możesz sobie na to pozwolić i musisz wycisnąć kilka dodatkowych cykli).

Biblioteki DLL systemu Windows naruszają jedną regułę definicji, ponieważ:

  • Twarde kodowanie, z którego biblioteki dynamicznej będzie używany symbol podczas statycznego łącza, i statyczne rozpoznawanie symboli w bibliotece, która je definiuje. Jeśli więc ten sam słaby symbol zostanie wygenerowany w wielu bibliotekach współdzielonych i tych bibliotekach, a nie użyty w jednym procesie, dynamiczny linker nie ma szans na scalenie tych symboli. Zazwyczaj takimi symbolami są elementy statyczne lub impedimenta klasy dla instancji szablonów, co powoduje problemy przy przekazywaniu instancji między kodem w różnych bibliotekach DLL.
  • Określają, czy symbol zostanie zaimportowany z biblioteki dynamicznej już podczas kompilacji. Zatem kod powiązany z pewną biblioteką statycznie jest niezgodny z kodem powiązanym dynamicznie z tą samą biblioteką.

Unix korzystający z eksportu w formacie ELF domyślnie importuje wszystkie wyeksportowane symbole, aby uniknąć pierwszego problemu i nie rozróżnia między statycznie i dynamicznie rozwiązanymi symbolami aż do statycznego czasu łącza, aby uniknąć drugiego.


Innym problemem są flagi kompilatora. Ten problem występuje w przypadku każdego programu złożonego z wielu jednostek kompilacyjnych, biblioteki dynamiczne nie muszą być zaangażowane. Jednak w systemie Windows jest znacznie gorzej. W systemie Unix tak naprawdę nie ma znaczenia, czy łączysz statycznie czy dynamicznie, nikt i tak nie łączy statycznego standardowego środowiska wykonawczego (w Linuksie może to nawet być nielegalne) i nie ma specjalnego środowiska uruchomieniowego debugowania, więc jedna kompilacja jest wystarczająca. Ale sposób, w jaki Microsoft wdrożył statyczne i dynamiczne łączenie, środowisko uruchomieniowe debugowania i zwalniania oraz kilka innych opcji oznacza, że ​​spowodowały one kombinatoryczną eksplozję potrzebnych wariantów bibliotek. Znów problem z platformą, a nie z językiem C ++.

Jan Hudec
źródło
2
@DougT .: GCC nie ma z tym nic wspólnego. Platforma ABI ma. W ELF, format obiektu używany przez większość Uniksów, biblioteki współdzielone eksportują wszystkie widoczne symbole i importują wszystkie symbole, które eksportują. Więc jeśli coś zostanie wygenerowane w wielu bibliotekach, dynamiczny linker użyje pierwszej definicji dla wszystkich. Prosty, elegancki i działający.
Jan Hudec
1
@MartinBa: Nie ma nic do scalenia, ale nie ma to znaczenia, o ile jest to to samo i dopóki nie ma zostać scalone. Tak, jeśli używasz niekompatybilnych ustawień kompilatora na platformie ELF, dostajesz taki sam bałagan jak wszędzie i wszędzie. Nawet jeśli nie korzystasz z bibliotek współdzielonych, więc jest to nieco nie na temat.
Jan Hudec
1
@ Jan - ma znaczenie dla Twojej odpowiedzi. Piszesz: „... reguła jednej definicji ... biblioteki DLL systemu Windows naruszają ten wymóg ... biblioteki współdzielone działają inaczej [na UNix] ...”, ale zadane pytanie dotyczy problemów z materiałami std-lib (zdefiniowanymi w nagłówkach) a powód, dla którego nie ma problemu na Unixie, nie ma nic wspólnego z SO vs. DLL, ale z faktem, że na Unixie (najwyraźniej) jest tylko jedna kompatybilna wersja standardowej biblioteki, podczas gdy w Windows MS wybrał wersje niekompatybilne (debugujące) (z rozszerzonym sprawdzaniem itp.).
Martin Ba
1
@MartinBa: Nie, głównym powodem problemu w systemie Windows jest to, że mechanizm eksportu / importu używany w systemie Windows nie może poprawnie scalić elementów statycznych i impedimenta klas klas szablonów we wszystkich przypadkach i nie może scalać symboli połączonych statycznie i dynamicznie. Jest to znacznie gorsze z powodu wielu wariantów biblioteki, ale głównym problemem jest to, że C ++ potrzebuje elastyczności od linkera, którego nie ma dynamiczny linker Windows.
Jan Hudec
4
Myślę, że ta implikacja, że ​​specyfikacja DLL jest zepsuta i odpowiadające jej żądanie Msft do „naprawy” jest niewłaściwe. Fakt, że biblioteki DLL nie obsługują niektórych funkcji C ++, nie jest wadą specyfikacji DLL. Biblioteki DLL to neutralny pod względem językowym, neutralny dla dostawcy mechanizm pakowania i ABI, które umożliwiają eksponowanie punktów wejścia na kod maszynowy („wywołania funkcji”) i obiekty BLOB danych. Nigdy nie miały natywnie wspierać zaawansowanych funkcji jakiegokolwiek konkretnego języka. To nie wina Msft ani specyfikacji DLL, że niektórzy ludzie chcą, aby były czymś innym.
Euro Micelli,
6

Nie.

Dużo pracy dzieje się w celu zastąpienia systemu nagłówka, funkcji zwanej modułami, która może mieć na to wpływ, ale z pewnością nie jest duża.

Klaim
źródło
2
Nie sądzę, żeby system nagłówków miał na to jakikolwiek wpływ. Problem polega na tym, że biblioteki DLL systemu Windows naruszają jedną regułę definicji (co oznacza, że ​​nie przestrzegają specyfikacji C ++, więc komitet C ++ nie może nic z tym zrobić) i że istnieje tak wiele wariantów standardowego środowiska wykonawczego w systemie Windows, które komitet C ++ może „ nic z tym zrobić.
Jan Hudec
1
Nie, nie robią tego. Jak mogliby, specyfikacja nawet nie wspomina czegoś takiego. Poza tym, gdy program (Windows) jest połączony z bibliotekami DLL systemu Windows, ODR jest spełnione: wszystkie widoczne (eksportowane) symbole muszą być zgodne z ODR.
Paul Michalik
@PaulMichalik C ++ obejmuje łączenie (faza 9) i wydaje mi się, że przynajmniej łączenie bibliotek DLL / SO w czasie ładowania mieści się w fazie 9. Oznacza to, że symbole z zewnętrznym łączeniem (eksportowane lub nie) powinny być łączone i zgodne ODR. Dynamiczne łączenie z LoadLibrary / dlopen oczywiście nie spełnia tych wymagań.
bames53
@ bames53: IMHO, specyfikacja jest zdecydowanie zbyt słaba, aby pozwolić na tego rodzaju stwierdzenia. Plik .dll / .so może być postrzegany jako „program” sam w sobie. Następnie zasady zostały spełnione. Coś jak ładowanie innych „programów” w czasie wykonywania jest tak niedoceniane przez standard, że wszelkie stwierdzenia dotyczące tego są dość arbitralne.
Paul Michalik
@PaulMichalik Jeśli plik wykonywalny wymaga łączenia w czasie ładowania, to przed łączeniem w czasie ładowania pozostają zewnętrzne nierozwiązane podmioty i brakuje informacji potrzebnych do wykonania. LoadLibrary i dlopen są poza specyfikacją, ale łączenie w czasie ładowania musi być wyraźnie widoczne w fazie 9.
bames53