Mengapa ISO / ANSI tidak membuat standar C ++ di tingkat biner? Ada banyak masalah portabilitas dengan C ++, yang hanya karena kurangnya standarisasi di tingkat biner.
Don Box menulis, (mengutip dari bukunya Essential COM , bab COM As A Better C ++ )
C ++ dan Portabilitas
Begitu keputusan dibuat untuk mendistribusikan kelas C ++ sebagai DLL, seseorang dihadapkan dengan salah satu kelemahan mendasar dari C ++ , yaitu, kurangnya standarisasi di tingkat biner . Meskipun Kertas Kerja ISO / ANSI C ++ Draft mencoba mengkodifikasi program mana yang akan dikompilasi dan apa efek semantik dari menjalankannya, ia tidak berusaha untuk menstandarisasi model runtime biner C ++. Pertama kali masalah ini menjadi jelas adalah ketika klien mencoba menautkan pustaka impor DLL FastString dari lingkungan pengembangan C ++ selain yang digunakan untuk membangun DLL FastString.
Apakah ada lebih banyak manfaat atau kehilangan kurangnya standarisasi biner ini?
Jawaban:
Bahasa dengan bentuk kompilasi biner yang kompatibel adalah fase yang relatif baru [*], misalnya runtime JVM dan .NET. Kompiler C dan C ++ biasanya memancarkan kode asli.
Keuntungannya adalah bahwa tidak perlu untuk JIT, atau penerjemah bytecode, atau VM, atau hal semacam itu. Misalnya, Anda tidak dapat menulis kode bootstrap yang berjalan pada permulaan mesin sebagai bytecode Java portabel, kecuali jika mesin tersebut dapat secara otomatis menjalankan bytecode Java, atau Anda memiliki semacam konverter dari Jawa ke file asli yang tidak kompatibel dengan biner kode yang dapat dieksekusi (dalam teori: tidak yakin ini dapat direkomendasikan dalam praktiknya untuk kode bootstrap). Anda bisa menulisnya di C ++, lebih atau kurang, meskipun tidak portabel C ++ bahkan di tingkat sumber, karena akan banyak mengotak-atik alamat perangkat keras ajaib.
Kerugiannya adalah tentu saja kode asli hanya berjalan sama sekali pada arsitektur yang dikompilasi, dan executable hanya dapat dimuat oleh loader yang memahami format executable mereka, dan hanya menghubungkan dengan dan memanggil executable lain untuk arsitektur yang sama dan ABI.
Bahkan jika Anda mencapai sejauh itu, menghubungkan dua executable bersama hanya akan benar-benar berfungsi dengan baik selama: (a) Anda tidak melanggar Aturan Satu Definisi, yang mudah dilakukan jika dikompilasi dengan berbagai kompiler / opsi / apa pun, sedemikian rupa sehingga mereka menggunakan definisi yang berbeda dari kelas yang sama (baik di header, atau karena mereka masing-masing secara statis terkait dengan implementasi yang berbeda); dan (b) semua detail implementasi yang relevan seperti tata letak struktur identik sesuai dengan opsi kompiler yang berlaku saat masing-masing dikompilasi.
Untuk standar C ++ untuk mendefinisikan semua ini akan menghapus banyak kebebasan yang saat ini tersedia untuk pelaksana. Implementer menggunakan kebebasan itu, terutama ketika menulis kode tingkat sangat rendah dalam C ++ (dan C, yang memiliki masalah yang sama).
Jika Anda ingin menulis sesuatu yang mirip C ++, untuk target biner-portable, ada C ++ / CLI, yang menargetkan .NET, dan Mono sehingga Anda dapat (mudah-mudahan) menjalankan .NET di tempat lain selain Windows. Saya pikir itu mungkin untuk membujuk kompiler MS untuk menghasilkan rakitan CIL murni yang akan berjalan pada Mono.
Ada juga hal-hal yang berpotensi dilakukan misalnya LLVM untuk menciptakan lingkungan biner-portable C atau C ++. Saya tidak tahu bahwa ada contoh luas yang muncul.
Tetapi ini semua bergantung pada perbaikan banyak hal yang membuat C ++ tergantung pada implementasi (seperti ukuran jenis). Maka lingkungan yang memahami binari portabel, harus tersedia pada sistem tempat kode dijalankan. Dengan mengizinkan binari non-portabel, C dan C ++ dapat pergi ke tempat-tempat di mana binari portabel tidak bisa, dan itulah sebabnya standar tidak mengatakan apa-apa tentang binari.
Kemudian pada platform apa pun, implementasi biasanya masih tidak memberikan kompatibilitas biner di antara set pilihan yang berbeda, meskipun standar tidak menghentikannya. Jika Don Box tidak suka bahwa kompiler Microsoft dapat menghasilkan binari yang tidak kompatibel dari sumber yang sama, sesuai dengan opsi kompiler, maka tim kompiler yang perlu dikeluhkan. Bahasa C ++ tidak melarang kompiler atau OS dari menjabarkan semua detail yang diperlukan, jadi setelah Anda membatasi diri Anda ke Windows itu bukan masalah mendasar dengan C ++. Microsoft memilih untuk tidak melakukannya.
Perbedaan sering dimanifestasikan sebagai satu hal lagi bahwa Anda dapat salah dan crash program Anda, tetapi mungkin ada banyak keuntungan yang dibuat dalam efisiensi antara, misalnya, versi debug vs rilis yang tidak kompatibel dll.
[*] Saya tidak yakin kapan ide itu pertama kali ditemukan, mungkin 1642 atau sesuatu, tetapi popularitas mereka saat ini relatif baru, dibandingkan dengan waktu ketika C ++ berkomitmen pada keputusan desain yang mencegahnya mendefinisikan portabilitas biner.
sumber
Kompatibilitas lintas-platform dan lintas-kompiler bukanlah tujuan utama di balik C dan C ++. Mereka lahir di era, dan dimaksudkan untuk tujuan yang minimalisasi platform-spesifik dan kompiler khusus waktu dan ruang.
Dari Stroustrup "Desain dan Evolusi C ++":
sumber
Ini bukan bug, ini fitur! Ini memberikan kebebasan implementator untuk mengoptimalkan implementasinya di tingkat biner. Little-endian i386 dan keturunannya bukan satu-satunya CPU yang memiliki atau memang ada.
sumber
Masalah yang dijelaskan dalam kutipan ini disebabkan oleh penghindaran standardisasi skema mangling simbol-nama yang disengaja (saya pikir " standardisasi di tingkat biner " adalah ungkapan yang menyesatkan dalam hal ini meskipun masalah ini terkait dengan Application Binary Interface kompiler ( ABI).
C ++ mengkodekan tanda tangan dan tipe informasi objek atau data, dan keanggotaan kelas / namespace ke dalam nama simbol, dan kompiler yang berbeda diizinkan untuk menggunakan skema yang berbeda. Akibatnya simbol dalam pustaka statis, DLL, atau file objek tidak akan ditautkan dengan kode yang dikompilasi menggunakan kompiler yang berbeda (atau bahkan mungkin versi berbeda dari kompiler yang sama).
Masalahnya dijelaskan dan dijelaskan mungkin lebih baik daripada yang saya bisa di sini , dengan contoh skema yang digunakan oleh kompiler yang berbeda.
Alasan kurangnya standarisasi secara sengaja juga dijelaskan di sini .
sumber
Tujuan ISO / ANSI adalah untuk menstandarkan bahasa C ++, masalah yang tampaknya cukup rumit untuk memerlukan bertahun - tahun untuk memiliki pembaruan standar bahasa dan dukungan kompiler.
Kompatibilitas biner jauh lebih kompleks, mengingat binari perlu dijalankan pada arsitektur CPU dan lingkungan OS yang berbeda.
sumber
Seperti yang Andy katakan, kompatibilitas lintas platform bukanlah tujuan besar, sedangkan platform luas dan implementasi perangkat keras adalah tujuan, dengan hasil bersih Anda dapat menulis implementasi yang sesuai untuk pemilihan sistem yang sangat luas. Standarisasi biner akan membuat ini praktis tidak bisa diraih.
Kompatibilitas C juga penting dan akan mempersulit ini.
Kemudian ada beberapa upaya untuk menstandarisasi ABI untuk sejumlah implementasi.
sumber
Saya pikir kurangnya standar untuk C + + adalah masalah di dunia saat ini, pemrograman modular de-coupled. Namun, kita harus mendefinisikan apa yang kita inginkan dari standar seperti itu.
Tidak seorang pun yang waras ingin mendefinisikan implementasi atau platform untuk biner. Jadi Anda tidak dapat mengambil x86 Windows dll dan mulai menggunakannya pada platform x86_64 Linux. Itu akan sedikit banyak.
Namun, apa yang orang inginkan adalah hal yang sama yang kita miliki dengan modul C - antarmuka standar pada tingkat biner (yaitu setelah dikompilasi). Saat ini, jika Anda ingin memuat dll dalam aplikasi modular, Anda mengekspor fungsi C dan mengikatnya saat runtime. Anda tidak dapat melakukannya dengan modul C ++. Akan lebih bagus jika Anda bisa, yang juga berarti bahwa dll yang ditulis dengan satu kompiler dapat dimuat oleh yang berbeda. Tentu, Anda masih tidak dapat memuat dll yang dibangun untuk platform yang tidak kompatibel, tapi itu bukan masalah yang perlu diperbaiki.
Jadi jika badan standar mendefinisikan antarmuka modul apa yang terbuka, maka kita akan memiliki lebih banyak fleksibilitas dalam memuat modul C ++, kita tidak perlu mengekspos kode C ++ sebagai kode C, dan kita mungkin akan mendapatkan lebih banyak menggunakan C ++ dalam bahasa skrip.
Kami juga tidak perlu menderita hal-hal seperti COM yang berupaya memberikan solusi untuk masalah ini.
sumber
Saya tidak berpikir ini sesederhana ini. Jawaban yang diberikan sudah memberikan dasar pemikiran yang sangat baik tentang kurangnya fokus pada standardisasi, tetapi C ++ mungkin terlalu kaya bahasa sehingga tidak cocok untuk benar-benar bersaing dengan C sebagai standar ABI.
Kita dapat menggunakan nama mangling yang dihasilkan dari fungsi yang berlebihan, ketidakcocokan vtable, ketidakcocokan dengan pengecualian yang melintasi batas-batas modul, dll. Semua ini benar-benar menyusahkan, dan saya berharap mereka setidaknya bisa menstandardisasi tata letak vtable.
Tetapi standar ABI bukan hanya tentang membuat C ++ dylibs yang diproduksi dalam satu kompiler yang mampu digunakan oleh biner lain yang dibangun oleh kompiler yang berbeda. ABI digunakan lintas bahasa . Akan lebih baik jika mereka setidaknya bisa membahas bagian pertama, tetapi tidak ada cara saya melihat C ++ benar-benar bersaing dengan C pada tingkat ABI universal yang sangat penting untuk membuat dylib yang paling kompatibel secara luas.
Bayangkan sepasang fungsi sederhana yang diekspor seperti ini:
... dan bayangkan
Foo
danBar
adalah kelas-kelas dengan konstruktor berparameter, menyalin konstruktor, memindahkan konstruktor, dan destruktor non-sepele.Kemudian ambil skenario dari Python / Lua / C # / Java / Haskell / etc. pengembang mencoba mengimpor modul ini dan menggunakannya dalam bahasa mereka.
Pertama kita akan membutuhkan standar nama mangling untuk cara mengekspor simbol menggunakan fungsi overloading. Ini adalah bagian yang lebih mudah. Namun seharusnya tidak benar-benar menjadi nama "mangling". Karena pengguna dylib harus mencari simbol berdasarkan nama, kelebihan di sini harus mengarah ke nama yang tidak terlihat berantakan. Mungkin nama simbolnya bisa seperti itu
"f_Foo"
"f_Bar_int"
atau semacamnya. Kami harus memastikan mereka tidak dapat berbenturan dengan nama yang sebenarnya ditentukan oleh pengembang, mungkin menyimpan beberapa simbol / karakter / konvensi untuk penggunaan ABI.Tapi sekarang skenario yang lebih sulit. Bagaimana pengembang Python, misalnya, memanggil konstruktor bergerak, menyalin konstruktor, dan destruktor? Mungkin kita bisa mengekspornya sebagai bagian dari dylib. Tetapi bagaimana jika
Foo
danBar
diekspor dalam modul yang berbeda? Haruskah kita menduplikasi simbol dan implementasi yang terkait dengan dylib ini atau tidak? Saya sarankan kita lakukan, karena mungkin sangat menjengkelkan sangat cepat kalau tidak untuk mulai harus terlibat dalam beberapa antarmuka dylib hanya untuk membuat objek di sini, kirimkan di sini, salin di sana, hancurkan di sini. Sementara perhatian dasar yang sama agak dapat diterapkan dalam C (hanya lebih secara manual / eksplisit), C cenderung menghindari ini hanya karena sifat cara orang memprogram dengannya.Ini hanyalah contoh kecil dari kecanggungan. Apa yang terjadi ketika salah satu
f
fungsi di atas melemparBazException
(juga kelas C ++ dengan konstruktor dan destruktor dan menurunkan std :: exception) ke dalam JavaScript?Paling-paling saya pikir kita hanya bisa berharap untuk membakukan ABI yang bekerja dari satu biner yang dihasilkan oleh satu kompiler C ++ ke biner lain yang diproduksi oleh yang lain. Itu akan bagus, tentu saja, tetapi saya hanya ingin menunjukkan ini. Biasanya menyertai masalah seperti itu untuk mendistribusikan perpustakaan umum yang bekerja lintas-kompiler juga sering keinginan untuk membuatnya benar-benar umum dan lintas-bahasa yang kompatibel.
Solusi yang Disarankan
Solusi saya yang disarankan setelah berjuang untuk menemukan cara untuk menggunakan antarmuka C ++ untuk API / ABI selama bertahun-tahun dengan antarmuka gaya COM adalah menjadi pengembang "C / C ++" (pun).
Gunakan C untuk membuat ABI universal tersebut, dengan C ++ untuk implementasinya. Kita masih bisa melakukan hal-hal seperti fungsi ekspor yang mengembalikan pointer ke kelas C ++ yang buram dengan fungsi eksplisit untuk membuat dan menghancurkan objek seperti itu di heap. Cobalah untuk jatuh cinta dengan estetika C itu dari perspektif ABI bahkan jika kita benar-benar menggunakan C ++ untuk implementasinya. Antarmuka abstrak dapat dimodelkan menggunakan tabel pointer fungsi. Sangat membosankan untuk membungkus barang-barang ini menjadi C API, tetapi manfaat dan kompatibilitas distribusi yang menyertainya akan cenderung membuatnya sangat berharga.
Kemudian jika kita tidak suka menggunakan antarmuka ini secara langsung (kita mungkin tidak seharusnya setidaknya karena alasan RAII), kita dapat membungkusnya semua yang kita inginkan di pustaka C ++ yang terhubung secara statis yang kami kirimkan dengan SDK. Klien C ++ dapat menggunakannya.
Klien Python tidak ingin menggunakan antarmuka C atau C ++ secara langsung karena tidak ada cara untuk membuat pythonique tersebut. Mereka ingin membungkusnya dengan antarmuka pythonique mereka sendiri, jadi sebenarnya hal yang baik bahwa kita hanya mengekspor minimum C API / ABI untuk membuatnya semudah mungkin.
Saya pikir banyak industri C ++ akan mendapat manfaat dari melakukan ini lebih daripada mencoba keras kepala mengirimkan antarmuka gaya COM dan sebagainya. Itu juga akan membuat seluruh hidup kita lebih mudah karena pengguna dylibs ini tidak perlu repot dengan ABI yang canggung. C membuatnya sederhana, dan kesederhanaan dari perspektif ABI memungkinkan kita untuk membuat API / ABI yang bekerja secara alami dan dengan minimalisme untuk semua jenis FFI.
sumber
Saya tidak tahu mengapa itu tidak standar pada tingkat biner. Tapi saya tahu apa yang saya lakukan. Pada Windows saya mendeklarasikan fungsi extern "C" BOOL WINAPI. (Tentu saja ganti BOOL dengan jenis apa pun fungsinya.) Dan mereka diekspor dengan bersih.
sumber
extern "C"
, itu akan menggunakan ABI C, yang standar de facto pada perangkat keras PC umum meskipun itu tidak dikenakan oleh komite apapun.Gunakan
unzip foo.zip && make foo.exe && foo.exe
jika Anda ingin portabilitas sumber Anda.sumber