Apakah aman untuk menautkan objek C ++ 17, C ++ 14, dan C ++ 11

101

Misalkan saya memiliki tiga objek yang dikompilasi, semuanya diproduksi 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, 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

ricab
sumber
6
Bukan pertanyaan sekolah / wawancara. Pertanyaannya berasal dari kasus tertentu: Saya sedang mengerjakan proyek yang bergantung pada perpustakaan sumber terbuka. Saya membangun pustaka ini dari sumber, tetapi sistem pembuatannya hanya menerima tanda untuk memilih antara bangunan C ++ 03 / C ++ 11. Kompiler yang saya gunakan mendukung standar lain, dan saya mempertimbangkan untuk mengupgrade proyek saya sendiri ke C ++ 17. Saya tidak yakin apakah ini keputusan yang aman. Bisakah ada jeda di ABI atau cara lain di mana pendekatan ini tidak disarankan? Saya tidak menemukan jawaban yang jelas dan memutuskan untuk mengajukan pertanyaan tentang kasus umum.
ricab
6
Ini sepenuhnya bergantung pada kompiler. Tidak ada spesifikasi C ++ formal yang mengatur situasi ini. Ada juga kemungkinan kecil bahwa kode yang ditulis ke standar C ++ 03 atau C + 11 akan memiliki beberapa masalah pada tingkat C ++ 14 dan C ++ 17. Dengan pengetahuan dan pengalaman yang memadai (dan kode yang ditulis dengan baik untuk memulai), masalah ini semestinya dapat diperbaiki. Namun, jika Anda tidak terlalu paham dengan standar C ++ yang lebih baru, lebih baik Anda tetap menggunakan apa yang didukung oleh sistem build, dan diuji untuk bekerja dengannya.
Sam Varshavchik
10
@Someprogrammerdude: Ini pertanyaan yang sangat berharga. Saya berharap saya punya jawaban. Yang saya tahu adalah bahwa libstdc ++ melalui RHEL devtoolset kompatibel dengan desain sebelumnya, dengan menghubungkan secara statis pada hal-hal yang lebih baru dan meninggalkan hal-hal yang lebih lama untuk diselesaikan secara dinamis saat runtime menggunakan libstdc ++ "asli" distro. Tapi itu tidak menjawab pertanyaannya.
Balapan Ringan di Orbit
4
@nm: ... yang sebagian besar terjadi ... hampir semua orang yang mendistribusikan pustaka C ++ independen distribusi melakukannya (1) dalam bentuk pustaka dinamis dan (2) tanpa wadah pustaka standar C ++ pada batas antarmuka. Pustaka yang berasal dari distribusi Linux memiliki kemudahan karena semuanya dibangun dengan kompiler yang sama, pustaka standar yang sama, dan kumpulan tanda default yang hampir sama.
Matteo Italia
3
Hanya untuk memperjelas komentar sebelumnya dari @MatteoItalia "dan saat beralih dari mode C ++ 03 ke C ++ 11 (khususnya std :: string)." Ini tidak benar, std::stringimplementasi aktif di libstdc ++ tidak bergantung pada -stdmode yang digunakan . Ini adalah properti penting, tepatnya untuk mendukung situasi seperti OP. Anda dapat menggunakan yang baru std::stringdalam kode C ++ 03, dan Anda dapat menggunakan yang lama std::stringdalam kode C ++ 11 (lihat tautan di komentar Matteo nanti).
Jonathan Wakely

Jawaban:

121

Kombinasi manakah dari objek-objek ini dan bukankah aman untuk ditautkan ke dalam satu biner? Mengapa?

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 -stdopsi) 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++11objek lain dengan GCC 5 dan -std=c++11Anda 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++17objek lain dengan GCC 8 dan -std=c++17Anda 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++.soversi):

  • 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, 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++17kemudian 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++.sopustaka 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 di libstdc++.sodari GCC 4.9 atau GCC 5. Demikian pula, jika Anda menautkan ke objek G, dibangun dengan GCC 8, Anda perlu menggunakan libstdc++.sodari 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 yangstd::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 menggunakan std::stringdan kegunaan lain std::__cxx11::string). Jika objek Anda menggunakan std::stringmaka biasanya mereka semua harus dikompilasi dengan implementasi string yang sama. Kompilasi dengan -D_GLIBCXX_USE_CXX11_ABI=0untuk memilih gcc4-compatibleimplementasi asli , atau -D_GLIBCXX_USE_CXX11_ABI=1untuk memilih cxx11implementasi baru (jangan tertipu oleh namanya, ini juga dapat digunakan di C ++ 03, ini disebutcxx11karena 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.

Jonathan Wakely
sumber
"karena mengompilasi bagian mana pun dari program dengan GCC 7 dapat menimbulkan ketergantungan pada simbol yang ada di libstdc ++. jadi dari GCC 4.9 atau GCC 5" yang Anda maksud TIDAK ada dari GCC 4.9 atau GCC 5, bukan? Apakah ini juga berlaku untuk tautan statis? Terima kasih atas info tentang kompatibilitas di seluruh versi kompilator.
Hadi Brais
1
Saya baru saja menyadari kesalahan besar dalam menawarkan hadiah untuk pertanyaan ini. 😂
Balapan Ringan di Orbit
4
@ricab Saya 90% yakin jawabannya sama untuk Clang / libc ++, tetapi saya tidak tahu tentang MSVC.
Jonathan Wakely
1
Jawaban ini luar biasa. Apakah itu didokumentasikan di suatu tempat bahwa 5.0+ stabil untuk 11/14?
Barry
1
Tidak terlalu jelas atau di satu tempat. gcc.gnu.org/gcc-5/changes.html#libstdcxx dan gcc.gnu.org/onlinedocs/libstdc++/manual/api.html#api.rel_51 menyatakan dukungan perpustakaan untuk C ++ 11 sebagai lengkap (bahasa Sebelumnya dukungan sudah dilengkapi fitur, tetapi masih "eksperimental"). Dukungan library C ++ 14 masih terdaftar sebagai eksperimental hingga 6.1, tetapi saya pikir dalam praktiknya tidak ada yang berubah antara 5.x dan 6.x yang memengaruhi ABI.
Jonathan Wakely
17

Ada dua bagian untuk menjawabnya. Kompatibilitas di tingkat compiler dan kompatibilitas di tingkat linker. Mari kita mulai dengan yang pertama.

anggap saja semua header ditulis dalam C ++ 11

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 __cplusplusdirektif. 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 __cplusplusdirektif mungkin berguna di sini.

Tiga klausul lainnya dari standar tersebut meliputi:

C.3.2 Klausul 3: konsep dasar

Ganti : Deallocator biasa (non-penempatan) baru

Rasional : Diperlukan untuk deallocation ukuran.

Efek pada fitur asli : Kode C ++ 2011 yang valid dapat mendeklarasikan fungsi alokasi penempatan global dan fungsi deallokasi 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 penghapusan operator mungkin cocok dengan penghapusan operator biasa (non-penempatan) yang telah ditentukan sebelumnya (3.7.4). Jika demikian, programnya tidak bagus, seperti untuk fungsi alokasi anggota kelas dan fungsi deallokasi (5.3.4).

C.3.3 Klausul 7: deklarasi

Ubah : fungsi anggota non-statis constexpr tidak secara implisit fungsi anggota const.

Rasional : Diperlukan untuk mengizinkan fungsi anggota constexpr untuk memutasi objek.

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

Misalnya, kode berikut ini valid di C ++ 2011 tetapi tidak valid dalam Standar Internasional ini karena mendeklarasikan fungsi anggota yang sama dua kali dengan tipe pengembalian berbeda:

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

C.3.4 Ayat 27: perpustakaan input / output

Perubahan : mendapat tidak ditentukan.

Rasional : Penggunaan get dianggap berbahaya.

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

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:

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 dari mainfungsi (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.

Hadi Brais
sumber
Pertanyaannya sebenarnya dimaksudkan untuk menghubungkan daripada kompilasi. Saya menyadari (berkat komentar ini ) yang mungkin tidak jelas dan telah mengeditnya untuk memperjelas bahwa setiap header yang disertakan memiliki interpretasi yang sama di ketiga standar.
ricab
@ricab Jawabannya mencakup kompilasi dan penautan. Saya pikir Anda bertanya tentang keduanya.
Hadi Brais
1
Memang, tapi menurut saya jawabannya terlalu panjang dan membingungkan, terutama sampai "Sekarang saya akan membahas kompatibilitas di level linker". Anda dapat mengganti semua yang di atas itu dengan sesuatu seperti jika tajuk yang disertakan tidak dapat didalilkan memiliki arti yang sama di C ++ 11 dan C ++ 14/17, maka tidak aman untuk menyertakannya di tempat pertama . Untuk bagian selanjutnya, apakah Anda memiliki sumber yang menunjukkan bahwa ketiga poin peluru tersebut adalah satu-satunya alasan potensial untuk ketidakcocokan? Bagaimanapun, terima kasih atas jawabannya, saya masih memberikan suara
ricab
@ab saya tidak bisa mengatakan dengan pasti. Itulah mengapa saya menambahkan peringatan di akhir jawaban. Orang lain dipersilakan untuk memperluas jawaban agar lebih tepat atau lengkap jika saya melewatkan sesuatu.
Hadi Brais
Ini membingungkan saya: "Menggunakan kompiler yang sama berarti bahwa header perpustakaan standar yang sama dan file sumber (...) akan digunakan". Bagaimana bisa demikian? Jika saya memiliki kode lama yang dikompilasi dengan gcc5, 'file kompilator' yang dimiliki versi itu tidak dapat menjadi bukti di masa mendatang. Untuk kode sumber yang dikompilasi pada waktu yang berbeda (secara liar) dengan versi kompilator yang berbeda, kita dapat yakin bahwa header perpustakaan dan file sumber berbeda. Dengan aturan Anda bahwa ini harus sama, Anda harus mengkompilasi ulang kode sumber lama dengan gcc5, ... dan pastikan mereka semua menggunakan 'file kompilator' terbaru (sama).
pengguna2943111
2

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.

E. Vakili
sumber
"Setiap versi kompilator dilengkapi dengan implementasi STL" Tidak, mereka tidak
Lightness Races di Orbit
@LightnessRacesinOrbit Maksud Anda tidak ada rasio antara misalnya libstdc ++ dan gcc?
E. Vakili
8
Tidak, maksud saya STL secara efektif telah usang selama lebih dari dua puluh tahun. Yang Anda maksud adalah Pustaka Standar C ++. Adapun jawaban selebihnya, dapatkah Anda memberikan referensi / bukti untuk mendukung klaim Anda? Saya pikir untuk pertanyaan seperti ini penting.
Balapan Ringan di Orbit
3
Maaf, tidak, tidak jelas dari teksnya. Anda telah membuat beberapa pernyataan yang menarik, tetapi belum mendukungnya dengan bukti apa pun.
Balapan Ringan di Orbit