Załóżmy, że mam trzy skompilowane obiekty, wszystkie wyprodukowane przez ten sam kompilator / wersję :
- A został skompilowany w standardzie C ++ 11
- B został skompilowany w standardzie C ++ 14
- C został skompilowany w standardzie C ++ 17
Dla uproszczenia załóżmy, że wszystkie nagłówki zostały napisane w C ++ 11, używając tylko konstrukcji, których semantyka nie zmieniła się między wszystkimi trzema wersjami standardowymi , a więc wszelkie współzależności zostały poprawnie wyrażone przez włączenie nagłówka, a kompilator nie zgłosił obiektu.
Która kombinacja tych obiektów to jest i czy nie jest bezpieczne połączenie w pojedynczy plik binarny? Czemu?
EDYCJA: mile widziane są odpowiedzi dotyczące głównych kompilatorów (np. Gcc, clang, vs ++)
std::string
implementacja w libstdc ++ jest niezależna od-std
używanego trybu . Jest to ważna właściwość, właśnie po to, by wspierać sytuacje takie jak PO. Możesz użyć nowegostd::string
kodu w C ++ 03 i możesz użyć staregostd::string
w kodzie C ++ 11 (zobacz link w późniejszym komentarzu Matteo).Odpowiedzi:
W przypadku GCC można bezpiecznie łączyć ze sobą dowolną kombinację obiektów A, B i C. Jeśli wszystkie są zbudowane w tej samej wersji, to są kompatybilne z ABI, wersja standardowa (tj.
-std
Opcja) nie robi żadnej różnicy.Czemu? Ponieważ jest to ważna właściwość naszej realizacji, nad którą ciężko pracujemy.
Problem występuje, gdy łączysz ze sobą obiekty skompilowane z różnymi wersjami GCC i używałeś niestabilnych funkcji nowego standardu C ++, zanim obsługa GCC dla tego standardu została zakończona. Na przykład, jeśli kompilujesz obiekt przy użyciu GCC 4.9
-std=c++11
i innego obiektu z GCC 5-std=c++11
, będziesz mieć problemy. Obsługa C ++ 11 była eksperymentalna w GCC 4.x, więc wystąpiły niezgodne zmiany pomiędzy wersjami GCC 4.9 i 5 funkcji C ++ 11. Podobnie, jeśli skompilujesz jeden obiekt z GCC 7 i-std=c++17
inny obiekt z GCC 8 i-std=c++17
będziesz miał problemy, ponieważ obsługa C ++ 17 w GCC 7 i 8 jest wciąż eksperymentalna i ewoluuje.Z drugiej strony będzie działać dowolna kombinacja następujących obiektów (chociaż zobacz uwagę poniżej dotyczącą
libstdc++.so
wersji):-std=c++03
-std=c++11
-std=c++17
Dzieje się tak, ponieważ obsługa C ++ 03 jest stabilna we wszystkich trzech używanych wersjach kompilatora, a więc komponenty C ++ 03 są kompatybilne między wszystkimi obiektami. Obsługa C ++ 11 jest stabilna od GCC 5, ale obiekt D nie używa żadnych funkcji C ++ 11, a obiekty E i F używają wersji, w których obsługa C ++ 11 jest stabilna. Obsługa C ++ 17 nie jest stabilna w żadnej z używanych wersji kompilatora, ale tylko obiekt F używa funkcji C ++ 17, więc nie ma problemu ze zgodnością z pozostałymi dwoma obiektami (jedyne funkcje, które współużytkują, pochodzą z C ++ 03 lub C ++ 11, a użyte wersje sprawiają, że te części są OK). Jeśli później chciałbyś skompilować czwarty obiekt, G, używając GCC 8, a
-std=c++17
następnie musiałbyś przekompilować F z tą samą wersją (lub bez połączenia z F), ponieważ symbole C ++ 17 w F i G są niekompatybilne.Jedynym zastrzeżeniem zgodności opisanej powyżej między D, E i F jest to, że twój program musi używać
libstdc++.so
wspólnej biblioteki z GCC 7 (lub nowszej). Ponieważ obiekt F został skompilowany z GCC 7, musisz użyć współdzielonej biblioteki z tego wydania, ponieważ kompilacja dowolnej części programu z GCC 7 może wprowadzić zależności od symboli, których nie ma wlibstdc++.so
GCC 4.9 lub GCC 5. Podobnie, jeśli połączyłeś się z obiektem G, zbudowanym z GCC 8, musisz użyćlibstdc++.so
GCC 8, aby upewnić się, że wszystkie symbole potrzebne G zostały znalezione. Prostą zasadą jest zapewnienie, że biblioteka współdzielona, której program używa w czasie wykonywania, jest co najmniej tak nowa, jak wersja użyta do kompilacji dowolnego z obiektów.Kolejnym zastrzeżeniem przy używaniu GCC, o którym wspomniano już w komentarzach do twojego pytania, jest to, że od GCC 5 istnieją dwie implementacje
std::string
dostępne w libstdc ++. Te dwie implementacje nie są kompatybilne z linkami (mają różne zniekształcone nazwy, więc nie można ich łączyć), ale mogą współistnieć w tym samym pliku binarnym (mają różne zniekształcone nazwy, więc nie powodują konfliktów, jeśli jeden obiekt używastd::string
i inne zastosowaniastd::__cxx11::string
). Jeśli twoje obiekty używają,std::string
zwykle powinny być skompilowane z tą samą implementacją łańcucha. Skompiluj z,-D_GLIBCXX_USE_CXX11_ABI=0
aby wybrać oryginalnągcc4-compatible
implementację lub-D_GLIBCXX_USE_CXX11_ABI=1
wybrać nowącxx11
implementację (nie daj się zwieść nazwie, może być również używany w C ++ 03, nazywa sięcxx11
ponieważ spełnia wymagania C ++ 11). To, która implementacja jest domyślna, zależy od tego, jak skonfigurowano GCC, ale wartość domyślną można zawsze zastąpić makrem w czasie kompilacji.źródło
Odpowiedź składa się z dwóch części. Zgodność na poziomie kompilatora i zgodność na poziomie konsolidatora. Zacznijmy od tego pierwszego.
Użycie tego samego kompilatora oznacza, że ten sam nagłówek biblioteki standardowej i pliki źródłowe (elementy skojarzone z kompilatorem) będą używane niezależnie od docelowego standardu C ++. Dlatego pliki nagłówkowe biblioteki standardowej są napisane tak, aby były kompatybilne ze wszystkimi wersjami C ++ obsługiwanymi przez kompilator.
To powiedziawszy, jeśli opcje kompilatora użyte do kompilacji jednostki tłumaczeniowej określają konkretny standard C ++, to wszelkie funkcje, które są dostępne tylko w nowszych standardach, nie powinny być dostępne. Odbywa się to za pomocą
__cplusplus
dyrektywy. Zobacz plik źródłowy wektorowy , aby zobaczyć interesujący przykład jego użycia. Podobnie kompilator odrzuci wszelkie funkcje składniowe oferowane przez nowsze wersje standardu.Wszystko to oznacza, że twoje założenie może dotyczyć tylko napisanych przez ciebie plików nagłówkowych. Te pliki nagłówkowe mogą powodować niezgodności, jeśli są zawarte w różnych jednostkach tłumaczeniowych przeznaczonych dla różnych standardów C ++. Jest to omówione w załączniku C standardu C ++. Są 4 klauzule, omówię tylko pierwszą, a resztę krótko wspomnę.
C.3.1 Klauzula 2: konwencje leksykalne
Pojedyncze cudzysłowy ograniczają literał znaku w C ++ 11, podczas gdy są separatorami cyfr w C ++ 14 i C ++ 17. Załóżmy, że masz następującą definicję makra w jednym z czystych plików nagłówkowych C ++ 11:
#define M(x, ...) __VA_ARGS__ // Maybe defined as a field in a template or a type. int x[2] = { M(1'2,3'4) };
Rozważ dwie jednostki tłumaczeniowe, które zawierają plik nagłówkowy, ale są przeznaczone odpowiednio dla C ++ 11 i C ++ 14. W przypadku języka C ++ 11 przecinek w cudzysłowie nie jest traktowany jako separator parametrów; jest tylko jeden parametr. Dlatego kod byłby równoważny z:
int x[2] = { 0 }; // C++11
Z drugiej strony, gdy celujemy w C ++ 14, pojedyncze cudzysłowy są interpretowane jako separatory cyfr. Dlatego kod byłby równoważny z:
int x[2] = { 34, 0 }; // C++14 and C++17
Chodzi o to, że użycie pojedynczych cudzysłowów w jednym z czystych plików nagłówkowych C ++ 11 może spowodować zaskakujące błędy w jednostkach tłumaczeniowych, które są przeznaczone dla C ++ 14/17. Dlatego nawet jeśli plik nagłówkowy jest napisany w C ++ 11, musi być napisany ostrożnie, aby zapewnić zgodność z późniejszymi wersjami standardu.
__cplusplus
Dyrektywa może być tu przydatna.Pozostałe trzy klauzule standardu to:
C.3.2 Rozdział 3: podstawowe pojęcia
C.3.3 Klauzula 7: deklaracje
C.3.4 Klauzula 27: biblioteka wejścia / wyjścia
Potencjalne niezgodności między C ++ 14 i C ++ 17 są omówione w C.4. Ponieważ wszystkie niestandardowe pliki nagłówkowe są napisane w C ++ 11 (jak określono w pytaniu), te problemy nie wystąpią, więc nie będę o nich tutaj wspominał.
Teraz omówię kompatybilność na poziomie konsolidatora. Ogólnie rzecz biorąc, potencjalne przyczyny niezgodności obejmują:
main
punkt wejścia.Jeśli format wynikowego pliku obiektowego zależy od docelowego standardu C ++, konsolidator musi mieć możliwość łączenia różnych plików obiektowych. W GCC, LLVM i VC ++ na szczęście tak nie jest. Oznacza to, że format plików obiektowych jest taki sam, niezależnie od docelowego standardu, chociaż w dużym stopniu zależy od samego kompilatora. W rzeczywistości żaden z linkerów GCC, LLVM i VC ++ nie wymaga wiedzy o docelowym standardzie C ++. Oznacza to również, że możemy łączyć pliki obiektowe, które są już skompilowane (łącząc statycznie środowisko wykonawcze).
Jeśli procedura uruchamiania programu (funkcja, która wywołuje
main
) jest inna dla różnych standardów C ++ i różne procedury nie są ze sobą kompatybilne, wówczas nie byłoby możliwe dowiązanie plików obiektowych. W GCC, LLVM i VC ++ na szczęście tak nie jest. Ponadto podpismain
funkcji (i ograniczenia, które się do niej stosuje, patrz sekcja 3.6 standardu) jest taki sam we wszystkich standardach C ++, więc nie ma znaczenia, w której jednostce tłumaczeniowej istnieje.Ogólnie WPO może nie działać dobrze z plikami obiektowymi skompilowanymi przy użyciu różnych standardów C ++. Zależy to dokładnie od tego, które etapy kompilatora wymagają znajomości docelowego standardu, a które nie, oraz od wpływu, jaki ma on na optymalizacje międzyprocedurowe, które krzyżują się z plikami obiektowymi. Na szczęście GCC, LLVM i VC ++ są dobrze zaprojektowane i nie mają tego problemu (nie jestem tego świadomy).
Dlatego GCC, LLVM i VC ++ zostały zaprojektowane tak, aby umożliwić zgodność binarną z różnymi wersjami standardu C ++. Nie jest to jednak wymóg samej normy.
Nawiasem mówiąc, chociaż kompilator VC ++ oferuje przełącznik std , który umożliwia kierowanie na określoną wersję standardu C ++, nie obsługuje kierowania na C ++ 11. Minimalną wersją, którą można określić, jest C ++ 14, która jest domyślną wersją począwszy od Visual C ++ 2013 Update 3. Możesz użyć starszej wersji VC ++ do kierowania na C ++ 11, ale wtedy będziesz musiał użyć innych kompilatorów VC ++ kompilowanie różnych jednostek tłumaczeniowych, które są przeznaczone dla różnych wersji standardu C ++, co co najmniej złamałoby WPO.
UWAGA: Moja odpowiedź może nie być kompletna lub bardzo precyzyjna.
źródło
Nowe standardy C ++ są podzielone na dwie części: funkcje językowe i komponenty bibliotek standardowych.
Jak rozumiesz przez nowy standard , zmiany w samym języku (np. Zakresowe dla) prawie nie stanowią problemu (czasami występują konflikty w nagłówkach bibliotek innych firm z nowszymi funkcjami języka standardowego).
Ale standardowa biblioteka ...
Każda wersja kompilatora zawiera implementację standardowej biblioteki C ++ (libstdc ++ z gcc, libc ++ z clang, standardowa biblioteka MS C ++ z VC ++, ...) i dokładnie jedną implementację, niewiele implementacji dla każdej standardowej wersji. Również w niektórych przypadkach możesz użyć innej implementacji biblioteki standardowej niż dostarczony przez kompilator. To, na co powinieneś zwrócić uwagę, to połączenie starszej implementacji biblioteki standardowej z nowszą.
Konflikt, który może wystąpić między bibliotekami stron trzecich a Twoim kodem, to biblioteka standardowa (i inne biblioteki), która łączy się z bibliotekami stron trzecich.
źródło