C ++: Kurangnya Standardisasi di Tingkat Biner

14

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?

Nawaz
sumber
Apakah ini lebih baik ditanyakan pada programmers.stackexchange.com , mengingat bagaimana ini lebih merupakan pertanyaan subyektif?
Stephen Furlani
1
Pertanyaan terkait saya sebenarnya: stackoverflow.com/questions/2083060/…
AraK
4
Don Box adalah orang yang fanatik. Abaikan dia.
John Dibling
8
Yah, C juga tidak distandarisasi oleh ANSI / ISO di tingkat biner; OTOH C memiliki standar ABI de facto daripada de jure . C ++ tidak memiliki ABI terstandarisasi karena pabrikan yang berbeda memiliki tujuan yang berbeda dengan implementasinya. Misalnya, pengecualian pada VC ++ piggyback di atas Windows SEH. POSIX tidak memiliki SEH dan oleh karena itu mengambil model itu tidak masuk akal (Jadi G ++ dan MinGW tidak menggunakan model itu).
Billy ONeal
3
Saya melihat ini sebagai fitur bukan kelemahan. Jika Anda mengikat implementasi ke ABI tertentu maka kami tidak akan pernah memiliki inovasi dan perangkat keras baru akan terikat pada desain bahasa (dan karena ada 15 tahun antara setiap versi baru yang lama di industri perangkat keras) dan dengan mencekik berinovasi ide-ide baru untuk membuat kode dieksekusi lebih efisien tidak akan dibuat. Harganya adalah bahwa semua kode dalam executable harus dibangun oleh kompiler / versi yang sama (masalah tetapi bukan yang utama).

Jawaban:

16

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.

Steve Jessop
sumber
@Steve Tapi C memiliki ABI yang terdefinisi dengan baik pada i386 dan AMD64, jadi saya bisa meneruskan pointer ke fungsi yang dikompilasi oleh GCC versi X ke fungsi yang dikompilasi oleh MSVC versi Y. Melakukan hal itu dengan fungsi C ++ tidak mungkin.
user877329
7

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 ++":

"Tujuan eksplisitnya adalah untuk mencocokkan C dalam hal run-time, kekompakan kode, dan kekompakan data. ... Yang ideal - yang dicapai - adalah bahwa C dengan Kelas dapat digunakan untuk apa pun yang dapat digunakan untuk C."

Andy Thomas
sumber
1
+1 - tepatnya. Bagaimana cara membangun ABI standar yang bekerja pada kotak ARM dan Intel? Tidak masuk akal!
Billy ONeal
1
sayangnya, gagal dalam hal ini. Anda dapat melakukan semua yang dilakukan C ... kecuali secara dinamis memuat modul C ++ saat runtime. Anda harus 'kembali' menggunakan fungsi C di antarmuka yang terbuka.
gbjbaanb
6

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
6

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 .

Clifford
sumber
3

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
Benar, tetapi masalah yang dijelaskan dalam kutipan sebenarnya tidak ada hubungannya dengan "kompatibilitas tingkat biner" (terlepas dari penggunaan istilah oleh penulis) dalam arti apa pun selain hal-hal tersebut didefinisikan dalam sesuatu yang disebut "Application Binary Interface". Dia sebenarnya menggambarkan masalah skema mangling nama yang tidak kompatibel.
@Clifford: skema susunan nama hanyalah sebagian dari kompatibilitas tingkat biner. yang terakhir lebih seperti istilah payung!
Nawaz
Saya ragu ada masalah dengan mencoba menjalankan biner Linux pada mesin windows. Banyak hal akan jauh lebih baik jika ada ABI per-platform, karena setidaknya bahasa skrip dapat secara dinamis memuat dan menjalankan biner pada platform yang sama, atau aplikasi dapat menggunakan komponen yang dibangun dengan kompiler yang berbeda. Anda tidak dapat menggunakan C dll di linux hari ini, dan tidak ada yang mengeluh, tetapi C dll itu masih dapat dimuat oleh aplikasi python yang mana manfaatnya bertambah.
gbjbaanb
2

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.

Flexo
sumber
Sial, saya lupa kompatibilitas C. Poin bagus, +1!
Andy Thomas
1

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.

gbjbaanb
sumber
1
+1. Ya saya setuju. Jawaban lain di sini pada dasarnya mengesampingkan masalah dengan mengatakan bahwa standardisasi biner akan melarang optimasi spesifik arsitektur. Tapi bukan itu intinya. Tidak ada yang berdebat untuk beberapa format executable biner lintas platform. Masalahnya adalah bahwa tidak ada antarmuka standar untuk memuat modul C ++ secara dinamis.
Charles Salvia
1

Ada banyak masalah portabilitas dengan C ++, yang hanya karena kurangnya standarisasi di tingkat biner.

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:

void f(Foo foo);
void f(Bar bar, int val);

... dan bayangkan Foodan Baradalah 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 Foodan Bardiekspor 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 ffungsi di atas melempar BazException(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
1
"Gunakan C untuk membuat ABI universal itu, dengan C ++ untuk implementasinya." ... Saya melakukan hal yang sama, seperti banyak orang lain!
Nawaz
-1

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.

mike jones
sumber
2
Tetapi jika Anda mendeklarasikannya extern "C", itu akan menggunakan ABI C, yang standar de facto pada perangkat keras PC umum meskipun itu tidak dikenakan oleh komite apapun.
Billy ONeal
-3

Gunakan unzip foo.zip && make foo.exe && foo.exejika Anda ingin portabilitas sumber Anda.

Sjoerd
sumber