Misalkan saya memiliki tiga objek yang dikompilasi, semuanya diproduksi oleh kompiler / versi yang sama :
- A dikompilasi dengan standar C ++ 11
- B dikompilasi dengan standar C ++ 14
- C dikompilasi dengan standar C ++ 17
Untuk kesederhanaan, anggaplah semua header ditulis dalam C ++ 11, hanya menggunakan konstruksi yang semantiknya tidak berubah di antara ketiga versi standar , sehingga setiap interdependensi diekspresikan dengan benar dengan penyertaan header dan compiler tidak menolak.
Kombinasi manakah dari objek-objek ini dan bukankah aman untuk ditautkan ke dalam satu biner? Mengapa?
EDIT: jawaban yang mencakup kompiler utama (misalnya gcc, clang, vs ++) dipersilakan
std::string
implementasi aktif di libstdc ++ tidak bergantung pada-std
mode yang digunakan . Ini adalah properti penting, tepatnya untuk mendukung situasi seperti OP. Anda dapat menggunakan yang barustd::string
dalam kode C ++ 03, dan Anda dapat menggunakan yang lamastd::string
dalam kode C ++ 11 (lihat tautan di komentar Matteo nanti).Jawaban:
Untuk GCC , aman untuk menautkan semua kombinasi objek A, B, dan C. Jika semuanya dibangun dengan versi yang sama maka kompatibel dengan ABI, versi standar (yaitu
-std
opsi) tidak membuat perbedaan apa pun.Mengapa? Karena itu adalah properti penting dari implementasi kami yang kami bekerja keras untuk memastikannya.
Di mana Anda mengalami masalah adalah jika Anda menautkan objek yang dikompilasi dengan versi GCC yang berbeda dan Anda telah menggunakan fitur yang tidak stabil dari standar C ++ baru sebelum dukungan GCC untuk standar tersebut selesai. Misalnya, jika Anda mengompilasi objek menggunakan GCC 4.9 dan
-std=c++11
objek lain dengan GCC 5 dan-std=c++11
Anda akan mengalami masalah. Dukungan C ++ 11 adalah eksperimental di GCC 4.x, sehingga ada perubahan yang tidak kompatibel antara fitur C ++ 11 versi GCC 4.9 dan 5. Demikian pula, jika Anda mengompilasi satu objek dengan GCC 7 dan-std=c++17
objek lain dengan GCC 8 dan-std=c++17
Anda akan mengalami masalah, karena dukungan C ++ 17 di GCC 7 dan 8 masih eksperimental dan berkembang.Di sisi lain, kombinasi apa pun dari objek berikut akan berfungsi (meskipun lihat catatan di bawah tentang
libstdc++.so
versi):-std=c++03
-std=c++11
-std=c++17
Ini karena dukungan C ++ 03 stabil di ketiga versi kompilator yang digunakan, sehingga komponen C ++ 03 kompatibel di antara semua objek. Dukungan C ++ 11 stabil sejak GCC 5, tetapi objek D tidak menggunakan fitur C ++ 11 apa pun, dan objek E dan F keduanya menggunakan versi yang dukungan C ++ 11 stabil. Dukungan C ++ 17 tidak stabil di salah satu versi compiler yang digunakan, tetapi hanya objek F yang menggunakan fitur C ++ 17 sehingga tidak ada masalah kompatibilitas dengan dua objek lainnya (satu-satunya fitur yang mereka bagikan berasal dari C ++ 03 atau C ++ 11, dan versi yang digunakan membuat bagian tersebut OK). Jika nanti Anda ingin mengkompilasi objek keempat, G, menggunakan GCC 8 dan
-std=c++17
kemudian Anda perlu mengkompilasi ulang F dengan versi yang sama (atau tidak tertaut ke F) karena simbol C ++ 17 di F dan G tidak kompatibel.Satu-satunya peringatan untuk kompatibilitas yang dijelaskan di atas antara D, E dan F adalah bahwa program Anda harus menggunakan
libstdc++.so
pustaka bersama dari GCC 7 (atau yang lebih baru). Karena objek F dikompilasi dengan GCC 7, Anda perlu menggunakan pustaka bersama dari rilis itu, karena mengompilasi bagian mana pun dari program dengan GCC 7 mungkin memperkenalkan dependensi pada simbol yang tidak ada dilibstdc++.so
dari GCC 4.9 atau GCC 5. Demikian pula, jika Anda menautkan ke objek G, dibangun dengan GCC 8, Anda perlu menggunakanlibstdc++.so
dari GCC 8 untuk memastikan semua simbol yang dibutuhkan oleh G ditemukan. Aturan sederhananya adalah memastikan pustaka bersama yang digunakan program pada waktu proses setidaknya sama baru dengan versi yang digunakan untuk mengompilasi objek apa pun.Peringatan lain saat menggunakan GCC, yang telah disebutkan dalam komentar pada pertanyaan Anda, adalah karena GCC 5 ada dua implementasi yang
std::string
tersedia di libstdc ++. Kedua implementasi tersebut tidak kompatibel dengan tautan (keduanya memiliki nama yang berbeda, jadi tidak dapat ditautkan bersama) tetapi dapat hidup berdampingan dalam biner yang sama (keduanya memiliki nama rusak yang berbeda, jadi jangan konflik jika satu objek menggunakanstd::string
dan kegunaan lainstd::__cxx11::string
). Jika objek Anda menggunakanstd::string
maka biasanya mereka semua harus dikompilasi dengan implementasi string yang sama. Kompilasi dengan-D_GLIBCXX_USE_CXX11_ABI=0
untuk memilihgcc4-compatible
implementasi asli , atau-D_GLIBCXX_USE_CXX11_ABI=1
untuk memilihcxx11
implementasi baru (jangan tertipu oleh namanya, ini juga dapat digunakan di C ++ 03, ini disebutcxx11
karena sesuai dengan persyaratan C ++ 11). Penerapan mana yang menjadi default bergantung pada bagaimana GCC dikonfigurasi, tetapi default selalu dapat diganti pada waktu kompilasi dengan makro.sumber
Ada dua bagian untuk menjawabnya. Kompatibilitas di tingkat compiler dan kompatibilitas di tingkat linker. Mari kita mulai dengan yang pertama.
Menggunakan kompilator yang sama berarti bahwa header perpustakaan standar dan file sumber yang sama (satu-satunya yang terkait dengan kompilator) akan digunakan terlepas dari standar C ++ target. Oleh karena itu, file header pustaka standar ditulis agar kompatibel dengan semua versi C ++ yang didukung oleh compiler.
Meskipun demikian, jika opsi compiler yang digunakan untuk mengompilasi unit terjemahan menetapkan standar C ++ tertentu, maka fitur apa pun yang hanya tersedia dalam standar yang lebih baru tidak boleh diakses. Ini dilakukan dengan menggunakan
__cplusplus
direktif. Lihat file sumber vektor untuk contoh menarik tentang cara penggunaannya. Demikian pula, kompilator akan menolak fitur sintaksis apa pun yang ditawarkan oleh versi standar yang lebih baru.Semua itu berarti asumsi Anda hanya dapat diterapkan ke file header yang Anda tulis. File header ini dapat menyebabkan ketidaksesuaian jika disertakan dalam unit terjemahan berbeda yang menargetkan standar C ++ berbeda. Ini dibahas dalam Lampiran C dari standar C ++. Ada 4 klausa, saya hanya akan membahas yang pertama, dan secara singkat menyebutkan sisanya.
C.3.1 Klausul 2: konvensi leksikal
Tanda kutip tunggal membatasi literal karakter dalam C ++ 11, sedangkan tanda kutip tunggal merupakan pemisah angka dalam C ++ 14 dan C ++ 17. Misalnya Anda memiliki definisi makro berikut di salah satu file header C ++ 11 murni:
#define M(x, ...) __VA_ARGS__ // Maybe defined as a field in a template or a type. int x[2] = { M(1'2,3'4) };
Pertimbangkan dua unit terjemahan yang menyertakan file header, tetapi masing-masing menargetkan C ++ 11 dan C ++ 14. Saat menargetkan C ++ 11, koma di dalam tanda kutip tidak dianggap sebagai pemisah parameter; hanya ada satu parameter. Oleh karena itu, kodenya akan sama dengan:
int x[2] = { 0 }; // C++11
Di sisi lain, saat menargetkan C ++ 14, tanda kutip tunggal diartikan sebagai pemisah digit. Oleh karena itu, kodenya akan sama dengan:
int x[2] = { 34, 0 }; // C++14 and C++17
Intinya di sini adalah bahwa menggunakan tanda kutip tunggal di salah satu file header C ++ 11 murni dapat menghasilkan bug yang mengejutkan dalam unit terjemahan yang menargetkan C ++ 14/17. Oleh karena itu, meskipun file header ditulis dalam C ++ 11, itu harus ditulis dengan hati-hati untuk memastikan bahwa itu kompatibel dengan versi standar yang lebih baru. The
__cplusplus
direktif mungkin berguna di sini.Tiga klausul lainnya dari standar tersebut meliputi:
C.3.2 Klausul 3: konsep dasar
C.3.3 Klausul 7: deklarasi
C.3.4 Ayat 27: perpustakaan input / output
Potensi ketidakcocokan antara C ++ 14 dan C ++ 17 dibahas di C.4. Karena semua file header non-standar ditulis dalam C ++ 11 (seperti yang ditentukan dalam pertanyaan), masalah ini tidak akan terjadi, jadi saya tidak akan menyebutkannya di sini.
Sekarang saya akan membahas kompatibilitas di tingkat linker. Secara umum, kemungkinan penyebab inkompatibilitas meliputi:
main
titik masuk.Jika format file objek yang dihasilkan bergantung pada standar C ++ target, penaut harus dapat menautkan file objek yang berbeda. Di GCC, LLVM, dan VC ++, untungnya tidak demikian. Artinya, format file objek adalah sama terlepas dari standar target, meskipun sangat bergantung pada kompiler itu sendiri. Nyatanya, tidak ada penaut GCC, LLVM, dan VC ++ yang membutuhkan pengetahuan tentang standar C ++ target. Ini juga berarti bahwa kita dapat menautkan file objek yang sudah dikompilasi (menghubungkan runtime secara statis).
Jika rutinitas startup program (fungsi yang memanggil
main
) berbeda untuk standar C ++ yang berbeda dan rutinitas yang berbeda tidak kompatibel satu sama lain, maka tidak mungkin untuk menautkan file objek. Di GCC, LLVM, dan VC ++, untungnya tidak demikian. Selain itu, tanda tangan darimain
fungsi (dan batasan yang berlaku padanya, lihat Bagian 3.6 dari standar) adalah sama di semua standar C ++, jadi tidak masalah di unit terjemahan mana itu ada.Secara umum, WPO mungkin tidak berfungsi dengan baik dengan file objek yang dikompilasi menggunakan standar C ++ yang berbeda. Hal ini bergantung pada tahapan mana dari compiler yang memerlukan pengetahuan tentang standar target dan tahapan mana yang tidak dan dampaknya pada pengoptimalan antar prosedural yang melintasi file objek. Untungnya, GCC, LLVM, dan VC ++ dirancang dengan baik dan tidak memiliki masalah ini (saya tidak menyadarinya).
Oleh karena itu, GCC, LLVM, dan VC ++ telah dirancang untuk mengaktifkan kompatibilitas biner di berbagai versi standar C ++. Ini sebenarnya bukan persyaratan standar itu sendiri.
Ngomong-ngomong, meskipun compiler VC ++ menawarkan std switch , yang memungkinkan Anda menargetkan versi tertentu dari standar C ++, compiler tersebut tidak mendukung penargetan C ++ 11. Versi minimum yang dapat ditentukan adalah C ++ 14, yang merupakan default mulai dari Visual C ++ 2013 Update 3. Anda dapat menggunakan versi lama VC ++ untuk menargetkan C ++ 11, tetapi kemudian Anda harus menggunakan kompiler VC ++ yang berbeda untuk mengkompilasi unit terjemahan berbeda yang menargetkan versi berbeda dari standar C ++, yang setidaknya akan merusak WPO.
PERHATIAN: Jawaban saya mungkin tidak lengkap atau sangat tepat.
sumber
Standar C ++ baru hadir dalam dua bagian: fitur bahasa dan komponen pustaka standar.
Seperti yang Anda maksud dengan standar baru , perubahan dalam bahasa itu sendiri (misalnya ranged-for) hampir tidak ada masalah (terkadang konflik ada di header perpustakaan pihak ketiga dengan fitur bahasa standar yang lebih baru).
Tapi perpustakaan standar ...
Setiap versi compiler dilengkapi dengan implementasi pustaka standar C ++ (libstdc ++ dengan gcc, libc ++ dengan clang, pustaka standar MS C ++ dengan VC ++, ...) dan tepat satu implementasi, tidak banyak implementasi untuk setiap versi standar. Juga dalam beberapa kasus Anda dapat menggunakan implementasi pustaka standar selain kompiler yang disediakan. Yang harus Anda perhatikan adalah menautkan implementasi pustaka standar lama dengan yang lebih baru.
Konflik yang dapat terjadi antara pustaka pihak ketiga dan kode Anda adalah pustaka standar (dan pustaka lain) yang menautkan ke pustaka pihak ketiga tersebut.
sumber