Pertanyaan Aman untuk menautkan objek C ++ 17, C ++ 14, dan C ++ 11


Misalkan saya memiliki tiga objek yang dikompilasi, semuanya dihasilkan oleh kompiler / versi yang sama:

  1. A dikompilasi dengan standar C ++ 11
  2. B dikompilasi dengan standar C ++ 14
  3. C dikompilasi dengan standar C ++ 17

Untuk kesederhanaan, mari kita asumsikan semua header ditulis dalam C ++ 11, hanya menggunakan konstruksi yang semantiknya tidak berubah antara ketiga versi standar, dan setiap interdependensi benar diungkapkan dengan inklusi header dan compiler tidak keberatan.

Kombinasi mana dari benda-benda ini dan tidak aman untuk ditautkan ke dalam satu biner? Mengapa?


EDIT: jawaban yang mencakup penyusun utama (mis. Gcc, clang, vs ++) dipersilakan


32
2017-10-14 16:36


asal


Jawaban:


Kombinasi mana dari benda-benda ini dan tidak aman untuk ditautkan ke dalam satu biner? Mengapa?

Untuk GCC aman untuk menghubungkan bersama setiap kombinasi objek A, B, dan C. Jika semuanya dibangun dengan versi yang sama maka mereka kompatibel dengan ABI, versi standar (yaitu -std pilihan) tidak ada bedanya.

Mengapa? Karena itulah properti penting dari implementasi kami yang kami bekerja keras untuk memastikannya.

Di mana Anda memiliki masalah adalah jika Anda menghubungkan bersama-sama objek yang dikompilasi dengan berbagai versi GCC dan Anda telah menggunakan fitur tidak stabil dari standar C ++ baru sebelum dukungan GCC untuk standar tersebut selesai. Sebagai contoh, jika Anda mengkompilasi suatu objek menggunakan GCC 4.9 dan -std=c++11 dan objek lain dengan GCC 5 dan -std=c++11 Anda akan memiliki masalah. Dukungan C ++ 11 adalah eksperimental di GCC 4.x, sehingga ada perubahan yang tidak kompatibel antara GCC versi 4.9 dan 5 versi C ++ 11. Demikian pula, jika Anda mengkompilasi satu objek dengan GCC 7 dan -std=c++17 dan objek lain dengan GCC 8 dan -std=c++17 Anda akan mengalami masalah, karena dukungan C ++ 17 di GCC 7 dan 8 masih bersifat eksperimental dan berkembang.

Di sisi lain, kombinasi dari benda-benda berikut akan bekerja (meskipun lihat catatan di bawah ini tentang libstdc++.so versi):

  • objek D dikompilasi dengan GCC 4.9 dan -std=c++03
  • objek E dikompilasi dengan GCC 5 dan -std=c++11
  • objek F dikompilasi dengan GCC 7 dan -std=c++17

Ini karena dukungan C ++ 03 stabil di ketiga versi kompilator yang digunakan, dan komponen C ++ 03 kompatibel antara semua objek. C + + 11 mendukung stabil sejak GCC 5, tetapi objek D tidak menggunakan fitur C ++ 11, dan objek E dan F keduanya menggunakan versi di mana dukungan C ++ 11 stabil. C + + 17 dukungan tidak stabil di salah satu versi kompiler 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 bagi berasal dari C ++ 03 atau C ++ 11, dan versi yang digunakan membuat bagian-bagian itu OK). Jika nantinya Anda ingin mengkompilasi objek keempat, G, gunakan GCC 8 dan -std=c++17 maka Anda perlu mengkompilasi ulang F dengan versi yang sama (atau bukan tautan ke F) karena simbol C ++ 17 di F dan G tidak kompatibel.

Satu-satunya pengecualian 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 lebih baru). Karena objek F dikompilasi dengan GCC 7, Anda perlu menggunakan pustaka bersama dari rilis itu, karena kompilasi bagian mana pun dari program dengan GCC 7 dapat memperkenalkan dependensi pada simbol yang tidak ada dalam libstdc++.sodari GCC 4.9 atau GCC 5. Demikian pula, jika Anda menautkan ke objek G, yang dibuat dengan GCC 8, Anda harus menggunakan libstdc++.so dari GCC 8 untuk memastikan semua simbol yang dibutuhkan oleh G ditemukan. Aturan sederhana adalah memastikan shared library yang digunakan program pada saat run-time setidaknya sama baru dengan versi yang digunakan untuk mengkompilasi objek manapun.

Peringatan lain ketika menggunakan GCC, sudah disebutkan di komentar pada pertanyaan Anda, adalah bahwa sejak GCC 5 ada dua implementasi dari std::string tersedia di libstdc ++. Kedua implementasi tidak kompatibel dengan tautan (mereka memiliki nama penganiayaan yang berbeda-beda, jadi tidak dapat ditautkan bersama) tetapi dapat berdampingan dalam biner yang sama (mereka memiliki nama penganiayaan yang berbeda-beda, jadi jangan berkonflik jika satu objek menggunakan std::string dan penggunaan lainnya std::__cxx11::string). Jika benda Anda digunakan std::string maka biasanya mereka semua harus dikompilasi dengan implementasi string yang sama. Kompilasi dengan -D_GLIBCXX_USE_CXX11_ABI=0 untuk memilih yang asli gcc4-compatible implementasi, atau -D_GLIBCXX_USE_CXX11_ABI=1 untuk memilih yang baru cxx11 implementasi (jangan terkecoh dengan nama, itu bisa digunakan di C ++ 03 juga, namanya cxx11 karena sesuai dengan persyaratan C ++ 11). Implementasi mana yang merupakan default tergantung pada bagaimana GCC dikonfigurasi, tetapi default selalu dapat dikompensasi pada waktu kompilasi dengan makro.


27
2018-03-05 21:38



Ada dua bagian untuk jawabannya. Kompatibilitas pada tingkat kompiler dan kompatibilitas pada level linker. Mari kita mulai dengan yang pertama.

mari kita asumsikan semua header ditulis dalam C ++ 11

Menggunakan kompilator yang sama berarti bahwa header perpustakaan standar dan file sumber yang sama (onces yang terkait dengan kompilator) akan digunakan terlepas dari standar C ++ target. Oleh karena itu, file header dari pustaka standar ditulis agar kompatibel dengan semua versi C ++ yang didukung oleh kompilator.

Yang mengatakan, jika opsi kompiler yang digunakan untuk mengkompilasi unit terjemahan menentukan standar C ++ tertentu, maka setiap fitur yang hanya tersedia dalam standar yang lebih baru tidak boleh diakses. Ini dilakukan dengan menggunakan __cplusplus direktif. Lihat vektor file sumber untuk contoh menarik tentang bagaimana itu digunakan. Demikian pula, kompiler akan menolak fitur sintaksis yang ditawarkan oleh versi standar yang lebih baru.

Semua itu berarti asumsi Anda hanya bisa berlaku untuk file header yang Anda tulis. File-file header ini dapat menyebabkan ketidakcocokan ketika dimasukkan dalam unit terjemahan berbeda yang menargetkan standar C ++ yang berbeda. Ini dibahas dalam Lampiran C standar C ++. Ada 4 klausa, saya hanya akan membahas yang pertama, dan secara singkat menyebutkan sisanya.

C.3.1 Klausa 2: konvensi leksikal

Kutipan tunggal membatasi karakter literal dalam C ++ 11, sedangkan mereka adalah pemisah digit dalam C ++ 14 dan C ++ 17. Asumsikan 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 target C ++ 11 dan C ++ 14, masing-masing. Saat menargetkan C ++ 11, koma dalam tanda kutip tidak dianggap sebagai pemisah parameter; hanya ada satu parameter. Oleh karena itu, kode akan setara dengan:

int x[2] = { 0 }; // C++11

Di sisi lain, ketika menargetkan C ++ 14, tanda kutip tunggal diartikan sebagai pemisah digit. Oleh karena itu, kode akan setara 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 menyebabkan bug yang mengejutkan di unit terjemahan yang menargetkan C ++ 14/17. Oleh karena itu, bahkan jika file header ditulis dalam C ++ 11, itu harus ditulis dengan hati-hati untuk memastikan bahwa itu kompatibel dengan versi standar yang lebih tinggi. Itu __cplusplus direktif mungkin berguna di sini.

Tiga klausa lainnya dari standar termasuk:

C.3.2 Klausa 3: konsep dasar

Perubahan: Baru deallocator biasa (non-penempatan)

Alasan: Diperlukan untuk dealokasi ukuran.

Efek pada fitur asli: Kode Valid C ++ 2011 dapat menyatakan fungsi alokasi penempatan global dan fungsi dealokasi sebagai berikut:

void operator new(std::size_t, std::size_t); 
void operator delete(void*, std::size_t) noexcept;

Dalam Standar Internasional ini, bagaimanapun, deklarasi operator   delete mungkin cocok dengan delete operator biasa (non-penempatan) yang telah ditetapkan   (3.7.4). Jika demikian, program ini tidak terbentuk, seperti untuk anggota kelas   fungsi alokasi dan fungsi dealokasi (5.3.4).

C.3.3 Klausul 7: deklarasi

Perubahan: fungsi anggota non-statik constexpr tidak secara implisit const   fungsi anggota.

Alasan: Diperlukan untuk memungkinkan fungsi anggota constexpr bermutasi   obyek.

Efek pada fitur asli: Kode Valid C ++ 2011 mungkin gagal dikompilasi dalam ini   Standar internasional.

Sebagai contoh, kode berikut ini berlaku di C ++ 2011 tetapi tidak valid di   Standar Internasional ini karena mendeklarasikan anggota yang sama   berfungsi dua kali dengan jenis pengembalian yang berbeda:

struct S {
constexpr const int &f();
int &f();
};

C.3.4 Klausul 27: pustaka masukan / keluaran

Perubahan: mendapat tidak didefinisikan.

Alasan: Penggunaan mendapat dianggap berbahaya.

Efek pada fitur asli: Kode C ++ 2011 valid yang menggunakan mendapat   fungsi mungkin gagal untuk dikompilasi dalam Standar Internasional ini.

Ketidakcocokan potensial antara C ++ 14 dan C ++ 17 dibahas dalam C.4. Karena semua file header non-standar ditulis dalam C ++ 11 (sebagaimana ditentukan dalam pertanyaan), masalah ini tidak akan terjadi, jadi saya tidak akan menyebutkannya di sini.

Sekarang saya akan membahas kompatibilitas pada level linker. Secara umum, kemungkinan alasan ketidakcocokan meliputi hal-hal berikut:

Jika format file objek yang dihasilkan bergantung pada standar C ++ target, penghubung harus dapat menautkan file objek yang berbeda. Di GCC, LLVM, dan VC ++, ini untungnya tidak terjadi. Artinya, format file objek adalah sama terlepas dari standar target, meskipun sangat tergantung pada kompilator itu sendiri. Faktanya, tidak ada satupun penghubung GCC, LLVM, dan VC ++ yang memerlukan pengetahuan tentang target C ++ standard. Ini juga berarti bahwa kita dapat menautkan file objek yang sudah dikompilasi (secara statis menautkan runtime).

Jika rutin 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 menghubungkan file objek. Di GCC, LLVM, dan VC ++, ini untungnya tidak terjadi. Selain itu, tanda tangan dari main fungsi (dan pembatasan yang berlaku di atasnya, lihat Bagian 3.6 dari standar) adalah sama di semua standar C ++, jadi tidak masalah di mana unit terjemahan itu ada.

Secara umum, WPO mungkin tidak berfungsi dengan baik dengan file objek yang dikompilasi menggunakan standar C ++ yang berbeda. Ini tergantung pada tahap mana dari kompiler yang membutuhkan pengetahuan tentang standar target dan tahapan mana yang tidak dan dampak yang ditimbulkannya pada optimasi inter-prosedural yang melintasi file objek. Untungnya, GCC, LLVM, dan VC ++ dirancang dengan baik dan tidak memiliki masalah ini (bukan yang saya ketahui).

Oleh karena itu, GCC, LLVM, dan VC ++ telah dirancang untuk diaktifkan biner kompatibilitas di berbagai versi standar C ++. Ini sebenarnya bukan persyaratan standar itu sendiri.

By the way, meskipun compiler VC ++ menawarkan beralih std, yang memungkinkan Anda untuk menargetkan versi tertentu dari standar C ++, itu 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 VC ++ yang lebih lama untuk menargetkan C ++ 11, tetapi kemudian Anda harus menggunakan compiler VC ++ yang berbeda. untuk mengkompilasi berbagai unit terjemahan yang menargetkan berbagai versi standar C ++, yang paling tidak akan merusak WPO.

CAVEAT: Jawaban saya mungkin tidak lengkap atau sangat tepat.


8
2018-03-05 20:26



Standar C ++ yang baru datang dalam dua bagian: fitur bahasa dan komponen pustaka standar.

Seperti yang Anda maksud dengan standar baru, perubahan bahasa itu sendiri (mis. range-for) hampir tidak ada masalah (terkadang konflik ada di header perpustakaan pihak ke-3 dengan fitur bahasa standar yang lebih baru).

Tapi pustaka standar ...

Setiap versi compiler dilengkapi dengan implementasi pustaka standar C ++ (libstdc ++ dengan gcc, libc ++ dengan clang, MS C ++ pustaka standar dengan VC ++, ...) dan tepat satu implementaion, tidak banyak implementasi untuk setiap versi standar. Juga dalam beberapa kasus Anda dapat menggunakan implementasi lain dari pustaka standar daripada compiler yang disediakan. Yang harus Anda perhatikan adalah menghubungkan implementasi pustaka standar lama dengan yang lebih baru.

Konflik yang dapat terjadi antara pustaka pihak ke-3 dan kode Anda adalah pustaka standar (dan pustaka lainnya) yang tertaut ke pustaka pihak ke-3 itu.


2
2018-03-05 10:15