Saya baru-baru ini melakukan wawancara dan satu pertanyaan yang diajukan adalah apa gunanya extern "C"
dalam kode C ++. Saya menjawab bahwa itu untuk menggunakan fungsi C dalam kode C ++ karena C tidak menggunakan nama-mangling. Saya ditanya mengapa C tidak menggunakan nama-mangling dan sejujurnya saya tidak bisa menjawab.
Saya memahami bahwa ketika kompilator C ++ mengompilasi fungsi, ia memberikan nama khusus untuk fungsi tersebut terutama karena kita dapat memiliki fungsi yang kelebihan beban dengan nama yang sama di C ++ yang harus diselesaikan pada waktu kompilasi. Di C, nama fungsinya akan tetap sama, atau mungkin dengan _ sebelumnya.
Pertanyaan saya adalah: apa yang salah dengan mengizinkan compiler C ++ untuk mengacaukan fungsi C juga? Saya akan berasumsi bahwa tidak masalah nama apa yang diberikan kompilator kepada mereka. Kami memanggil fungsi dengan cara yang sama di C dan C ++.
sumber
extern "C"
mengatakan untuk mengacaukan nama dengan cara yang sama seperti yang dilakukan oleh compiler C ".Jawaban:
Itu semacam jawaban di atas, tetapi saya akan mencoba memasukkan semuanya ke dalam konteks.
Pertama, C datang lebih dulu. Dengan demikian, apa yang dilakukan C adalah, semacam, "default". Itu tidak merusak nama karena memang tidak. Nama fungsi adalah nama fungsi. Global adalah global, dan seterusnya.
Kemudian C ++ muncul. C ++ ingin dapat menggunakan penaut yang sama dengan C, dan dapat menautkan dengan kode yang ditulis dalam C. Tetapi C ++ tidak dapat meninggalkan C "mangling" (atau, kekurangannya) sebagaimana adanya. Lihat contoh berikut:
int function(int a); int function();
Dalam C ++, ini adalah fungsi yang berbeda, dengan badan yang berbeda. Jika tidak ada satupun yang rusak, keduanya akan disebut "fungsi" (atau "_fungsi"), dan penaut akan mengeluh tentang definisi ulang simbol. Solusi C ++ adalah untuk mengacaukan tipe argumen ke dalam nama fungsi. Jadi, yang satu dipanggil
_function_int
dan yang lainnya dipanggil_function_void
(bukan skema mangling yang sebenarnya) dan tabrakan dihindari.Sekarang kita punya masalah. Jika
int function(int a)
didefinisikan dalam modul C, dan kita hanya mengambil headernya (yaitu deklarasi) dalam kode C ++ dan menggunakannya, kompilator akan membuat instruksi ke linker untuk diimpor_function_int
. Ketika fungsi didefinisikan, dalam modul C, itu tidak disebut itu. Itu disebut_function
. Ini akan menyebabkan kesalahan linker.Untuk menghindari kesalahan itu, selama deklarasi fungsi, kami memberi tahu kompilator bahwa itu adalah fungsi yang dirancang untuk ditautkan, atau dikompilasi oleh, kompilator C:
extern "C" int function(int a);
Kompilator C ++ sekarang tahu cara mengimpor
_function
daripada_function_int
, dan semuanya baik-baik saja.sumber
-std=c++11
, dan menghindari penggunaan apa pun di luar standar. Itu sama dengan mendeklarasikan versi Java (meskipun versi Java yang lebih baru kompatibel dengan versi sebelumnya). Ini bukan kesalahan standar orang menggunakan ekstensi spesifik compiler dan kode yang bergantung pada platform. Di sisi lain, Anda tidak dapat menyalahkan mereka, karena ada banyak hal (khususnya IO, seperti soket) yang hilang dalam standar. Panitia tampaknya perlahan mengejar itu. Koreksi saya jika saya melewatkan sesuatu.Ini bukan berarti bahwa mereka "tidak bisa", mereka tidak , pada umumnya.
Jika Anda ingin memanggil fungsi dalam pustaka C yang dipanggil
foo(int x, const char *y)
, tidak ada gunanya membiarkan compiler C ++ Anda mengacaukannyafoo_I_cCP()
(atau apa pun, hanya membuat skema mangling di tempat di sini) hanya karena itu bisa.Nama itu tidak akan menyelesaikan, fungsinya ada di C dan namanya tidak bergantung pada daftar tipe argumennya. Jadi compiler C ++ harus mengetahui hal ini, dan menandai fungsinya sebagai C untuk menghindari melakukan mangling.
Ingat bahwa fungsi C tersebut mungkin ada di pustaka yang kode sumbernya tidak Anda miliki, yang Anda miliki hanyalah biner dan header yang telah dikompilasi sebelumnya. Jadi compiler C ++ Anda tidak bisa melakukan "itu sendiri", itu tidak bisa mengubah apa yang ada di perpustakaan.
sumber
extern "C"
:)Mereka tidak akan menjadi fungsi C lagi.
Fungsi bukan hanya tanda tangan dan definisi; bagaimana suatu fungsi bekerja sangat ditentukan oleh faktor-faktor seperti konvensi pemanggilan. "Application Binary Interface" yang ditentukan untuk digunakan pada platform Anda menjelaskan bagaimana sistem berbicara satu sama lain. C ++ ABI yang digunakan oleh sistem Anda menetapkan skema mangling nama, sehingga program pada sistem tersebut mengetahui cara memanggil fungsi di perpustakaan dan sebagainya. (Baca C ++ Itanium ABI untuk contoh yang bagus. Anda akan segera mengetahui mengapa itu perlu.)
Hal yang sama berlaku untuk C ABI di sistem Anda. Beberapa C ABI sebenarnya memiliki skema mangling nama (mis. Visual Studio), jadi ini bukan tentang "mematikan mangling nama" dan lebih banyak tentang beralih dari C ++ ABI ke C ABI, untuk fungsi tertentu. Kami menandai fungsi C sebagai fungsi C, yang terkait dengan C ABI (bukan C ++ ABI). Deklarasi harus sesuai dengan definisi (baik itu dalam proyek yang sama atau di beberapa perpustakaan pihak ketiga), jika tidak, deklarasi tidak ada gunanya. Tanpa itu, sistem Anda tidak akan tahu bagaimana menemukan / menjalankan fungsi-fungsi itu.
Adapun mengapa platform tidak mendefinisikan C dan C ++ ABI menjadi sama dan menyingkirkan "masalah" ini, itu sebagian bersifat historis - C ABI asli tidak cukup untuk C ++, yang memiliki ruang nama, kelas, dan operator yang kelebihan beban, semuanya yang entah bagaimana perlu direpresentasikan dalam nama simbol dengan cara yang ramah komputer - tetapi orang mungkin juga berpendapat bahwa membuat program C sekarang mematuhi C ++ tidak adil bagi komunitas C, yang harus menghadapi masalah yang jauh lebih rumit ABI hanya demi sebagian orang lain yang menginginkan interoperabilitas.
sumber
+int(PI/3)
, Tetapi dengan satu butir garam: aku akan sangat berhati-hati untuk berbicara tentang "C ++ ABI" ... AFAIK, ada upaya untuk mendefinisikan C ++ ABI, tapi tidak ada yang nyata de facto / de jure standar - sebagai isocpp.org/files /papers/n4028.pdf menyatakan (dan saya sepenuhnya setuju), kutipan, sungguh ironis bahwa C ++ sebenarnya selalu mendukung cara untuk mempublikasikan API dengan ABI biner yang stabil — dengan menggunakan subset C dari C ++ melalui “C ". .C++ Itanium ABI
hanya itu - beberapa C ++ ABI untuk Itanium ... seperti yang dibahas di stackoverflow.com/questions/7492180/c-abi-issues-listMSVC sebenarnya tidak mengacaukan nama C, meski dengan cara yang sederhana. Kadang-kadang ditambahkan
@4
atau nomor kecil lainnya. Ini terkait dengan konvensi pemanggilan dan kebutuhan untuk pembersihan tumpukan.Jadi premisnya cacat.
sumber
_
?/Gd, /Gr, /Gv, /Gz
. (Artinya, konvensi pemanggilan standar adalah yang digunakan kecuali jika deklarasi fungsi secara eksplisit menetapkan konvensi pemanggilan.). Anda sedang memikirkan__cdecl
yang merupakan konvensi panggilan standar default.Sangat umum untuk memiliki program yang sebagian ditulis dalam C dan sebagian ditulis dalam beberapa bahasa lain (seringkali bahasa assembly, tapi terkadang Pascal, FORTRAN, atau yang lainnya). Ini juga umum untuk memiliki program yang berisi komponen berbeda yang ditulis oleh orang berbeda yang mungkin tidak memiliki kode sumber untuk semuanya.
Pada kebanyakan platform, terdapat spesifikasi - sering disebut ABI [Application Binary Interface] yang menjelaskan apa yang harus dilakukan kompilator untuk menghasilkan fungsi dengan nama tertentu yang menerima argumen dari beberapa tipe tertentu dan mengembalikan nilai dari beberapa tipe tertentu. Dalam beberapa kasus, ABI dapat mendefinisikan lebih dari satu "konvensi pemanggil"; kompiler untuk sistem semacam itu sering kali menyediakan sarana untuk menunjukkan konvensi pemanggilan mana yang harus digunakan untuk fungsi tertentu. Misalnya, di Macintosh, kebanyakan rutinitas Toolbox menggunakan konvensi panggilan Pascal, jadi prototipe untuk sesuatu seperti "LineTo" akan menjadi seperti ini:
/* Note that there are no underscores before the "pascal" keyword because the Toolbox was written in the early 1980s, before the Standard and its underscore convention were published */ pascal void LineTo(short x, short y);
Jika semua kode dalam sebuah proyek dikompilasi menggunakan kompilator yang sama, tidak masalah apa nama kompilator yang diekspor untuk setiap fungsi, tetapi dalam banyak situasi akan diperlukan kode C untuk memanggil fungsi yang dikompilasi menggunakan alat lain dan tidak dapat dikompilasi ulang dengan kompilator saat ini [dan bahkan mungkin tidak dalam C]. Oleh karena itu, kemampuan untuk menentukan nama penaut sangat penting untuk penggunaan fungsi tersebut.
sumber
Saya akan menambahkan satu jawaban lain, untuk membahas beberapa diskusi tangensial yang terjadi.
C ABI (application binary interface) awalnya dipanggil untuk meneruskan argumen pada stack dalam urutan terbalik (yaitu - didorong dari kanan ke kiri), di mana pemanggil juga membebaskan penyimpanan stack. ABI modern sebenarnya menggunakan register untuk meneruskan argumen, tetapi banyak pertimbangan yang merusak kembali ke argumen stack asli yang diteruskan.
Pascal ABI yang asli, sebaliknya, mendorong argumen dari kiri ke kanan, dan callee harus melontarkan argumen. ABI C asli lebih unggul dari ABI Pascal asli dalam dua poin penting. Argumen push order berarti bahwa offset tumpukan dari argumen pertama selalu diketahui, memungkinkan fungsi yang memiliki jumlah argumen yang tidak diketahui, di mana argumen awal mengontrol berapa banyak argumen lain yang ada (ala
printf
).Cara kedua di mana C ABI lebih unggul adalah perilaku jika penelepon dan penerima tidak setuju tentang berapa banyak argumen yang ada. Dalam kasus C, selama Anda tidak benar-benar mengakses argumen setelah yang terakhir, tidak ada hal buruk yang terjadi. Di Pascal, jumlah argumen yang salah muncul dari tumpukan, dan seluruh tumpukan rusak.
Windows 3.1 ABI asli didasarkan pada Pascal. Karena itu, ia menggunakan Pascal ABI (argumen dalam urutan kiri ke kanan, callee pops). Karena ketidakcocokan dalam nomor argumen dapat menyebabkan korupsi tumpukan, skema kerusakan dibentuk. Setiap nama fungsi dihancurkan dengan angka yang menunjukkan ukuran, dalam byte, dari argumennya. Jadi, pada mesin 16 bit, fungsi berikut (sintaks C):
int function(int a)
Hancur
function@2
, karenaint
lebarnya dua byte. Hal ini dilakukan agar jika deklarasi dan definisi tidak cocok, linker akan gagal menemukan fungsi daripada merusak tumpukan pada waktu proses. Sebaliknya, jika program menautkan, maka Anda bisa yakin jumlah byte yang benar dikeluarkan dari tumpukan di akhir panggilan.32 bit Windows dan selanjutnya gunakan
stdcall
ABI sebagai gantinya. Mirip dengan Pascal ABI, hanya saja urutan push seperti di C, dari kanan ke kiri. Seperti Pascal ABI, nama mangling mengubah ukuran byte argumen menjadi nama fungsi untuk menghindari korupsi tumpukan.Tidak seperti klaim yang dibuat di tempat lain di sini, C ABI tidak merusak nama fungsi, bahkan di Visual Studio. Sebaliknya, fungsi mangling yang didekorasi dengan
stdcall
spesifikasi ABI tidak unik untuk VS. GCC juga mendukung ABI ini, bahkan saat mengompilasi untuk Linux. Ini digunakan secara luas oleh Wine , yang menggunakan loadernya sendiri untuk memungkinkan penautan waktu proses dari binari terkompilasi Linux ke DLL yang dikompilasi Windows.sumber
Compiler C ++ menggunakan name mangling untuk memungkinkan nama simbol unik untuk fungsi yang kelebihan beban yang tanda tangannya akan sama. Ini pada dasarnya mengkodekan jenis argumen juga, yang memungkinkan polimorfisme pada tingkat berbasis fungsi.
C tidak memerlukan ini karena tidak memungkinkan untuk kelebihan beban fungsi.
Perhatikan bahwa mangling nama adalah salah satu (tapi tentu saja bukan satu-satunya!) Alasan bahwa seseorang tidak dapat mengandalkan 'C ++ ABI'.
sumber
C ++ ingin dapat berinteraksi dengan kode C yang tertaut dengannya, atau yang ditautkan dengannya.
C mengharapkan nama fungsi yang tidak dirusak nama.
Jika C ++ merusaknya, ia tidak akan menemukan fungsi non-rusak yang diekspor dari C, atau C tidak akan menemukan fungsi yang diekspor C ++. C linker harus mendapatkan nama yang diharapkannya sendiri, karena tidak tahu itu berasal dari atau menuju ke C ++.
sumber
Mengolah nama-nama fungsi dan variabel C akan memungkinkan tipenya diperiksa pada waktu tautan. Saat ini, semua (?) Implementasi C memungkinkan Anda untuk mendefinisikan variabel dalam satu file dan menyebutnya sebagai fungsi di file lain. Atau Anda dapat mendeklarasikan fungsi dengan tanda tangan yang salah (misalnya
void fopen(double)
, lalu memanggilnya.Saya mengusulkan skema untuk hubungan tipe-aman dari variabel dan fungsi C melalui penggunaan mangling pada tahun 1991. Skema ini tidak pernah diadopsi, karena, seperti yang telah disebutkan di sini, ini akan menghancurkan kompatibilitas ke belakang.
sumber