C dan C ++ mirip secara dangkal, tetapi masing-masing dikompilasi menjadi sekumpulan kode yang sangat berbeda. Saat Anda menyertakan file header dengan kompilator C ++, kompilator mengharapkan kode C ++. Namun, jika ini adalah header C, maka compiler mengharapkan data yang terdapat dalam file header akan dikompilasi ke format tertentu — C ++ 'ABI', atau 'Application Binary Interface', sehingga linker tersendat. Ini lebih disukai daripada meneruskan data C ++ ke fungsi yang mengharapkan data C.
(Untuk masuk ke seluk-beluknya, ABI C ++ umumnya 'mengacaukan' nama fungsi / metode mereka, jadi memanggil printf()
tanpa menandai prototipe sebagai fungsi C, C ++ sebenarnya akan menghasilkan pemanggilan kode _Zprintf
, ditambah omong kosong tambahan di akhir. )
Jadi: gunakan extern "C" {...}
saat menyertakan header ac — sesederhana itu. Jika tidak, Anda akan mengalami ketidakcocokan dalam kode yang dikompilasi, dan linker akan tersedak. Namun, untuk sebagian besar header, Anda bahkan tidak memerlukannya extern
karena sebagian besar header sistem C sudah memperhitungkan fakta bahwa mereka mungkin disertakan oleh kode C ++ dan sudah extern
kodenya.
#ifdef __cplusplus extern "C" { #endif
Jadi ketika dimasukkan dari file C ++ mereka masih diperlakukan sebagai header C.extern "C" menentukan bagaimana simbol dalam file objek yang dihasilkan harus diberi nama. Jika suatu fungsi dideklarasikan tanpa extern "C", maka nama simbol pada file objek akan menggunakan nama C ++ mangling. Berikut contohnya.
Diberikan test. C seperti ini:
void foo() { }
Mengompilasi dan membuat daftar simbol dalam file objek memberikan:
$ g++ -c test.C $ nm test.o 0000000000000000 T _Z3foov U __gxx_personality_v0
Fungsi foo sebenarnya disebut "_Z3foov". String ini berisi antara lain informasi tipe untuk tipe kembalian dan parameter. Jika Anda malah menulis test. C seperti ini:
extern "C" { void foo() { } }
Kemudian kompilasi dan lihat simbol:
$ g++ -c test.C $ nm test.o U __gxx_personality_v0 0000000000000000 T foo
Anda mendapatkan hubungan C. Nama fungsi "foo" di file objek hanyalah "foo", dan tidak memiliki semua info tipe mewah yang berasal dari nama mangling.
Anda biasanya menyertakan tajuk dalam extern "C" {} jika kode yang menyertainya dikompilasi dengan compiler C tetapi Anda mencoba memanggilnya dari C ++. Saat Anda melakukan ini, Anda memberi tahu kompiler bahwa semua deklarasi di header akan menggunakan tautan C. Saat Anda menautkan kode, file .o Anda akan berisi referensi ke "foo", bukan "_Z3fooblah", yang diharapkan cocok dengan apa pun yang ada di pustaka yang Anda tautkan.
Sebagian besar perpustakaan modern akan menempatkan pelindung di sekitar tajuk tersebut sehingga simbol dinyatakan dengan tautan yang tepat. misalnya di banyak tajuk standar, Anda akan menemukan:
#ifdef __cplusplus extern "C" { #endif ... declarations ... #ifdef __cplusplus } #endif
Ini memastikan bahwa ketika kode C ++ menyertakan header, simbol di file objek Anda cocok dengan yang ada di pustaka C. Anda hanya perlu meletakkan "C" {} eksternal di sekitar header C Anda jika sudah tua dan belum memiliki pelindung ini.
sumber
Di C ++, Anda dapat memiliki entitas berbeda yang memiliki nama yang sama. Misalnya di sini adalah daftar fungsi yang semuanya bernama foo :
A::foo()
B::foo()
C::foo(int)
C::foo(std::string)
Untuk membedakan semuanya, compiler C ++ akan membuat nama unik untuk masing-masing dalam proses yang disebut name-mangling atau decorating. Kompiler C tidak melakukan ini. Selain itu, setiap kompiler C ++ dapat melakukan ini dengan cara yang berbeda.
extern "C" memberitahu compiler C ++ untuk tidak melakukan pengubahan nama pada kode di dalam tanda kurung. Ini memungkinkan Anda memanggil fungsi C dari dalam C ++.
sumber
Ini berkaitan dengan cara kompiler yang berbeda melakukan pengubahan nama. Kompiler C ++ akan mengacaukan nama simbol yang diekspor dari file header dengan cara yang sama sekali berbeda dari kompiler C, jadi ketika Anda mencoba menautkan, Anda akan mendapatkan kesalahan tautan yang mengatakan ada simbol yang hilang.
Untuk mengatasi ini, kami memberi tahu compiler C ++ untuk dijalankan dalam mode "C", sehingga compiler tersebut menjalankan name mangling dengan cara yang sama seperti compiler C. Setelah melakukannya, kesalahan penaut diperbaiki.
sumber
C dan C ++ memiliki aturan berbeda tentang nama simbol. Simbol adalah bagaimana linker mengetahui bahwa panggilan ke fungsi "openBankAccount" dalam satu file objek yang dihasilkan oleh compiler adalah referensi ke fungsi yang Anda sebut "openBankAccount" di file objek lain yang dihasilkan dari file sumber yang berbeda dengan file yang sama (atau kompatibel) penyusun. Hal ini memungkinkan Anda untuk membuat program dari lebih dari satu file sumber, yang melegakan saat mengerjakan proyek besar.
Di C aturannya sangat sederhana, simbol semua ada dalam satu ruang nama. Jadi integer "socks" disimpan sebagai "socks" dan fungsi count_socks disimpan sebagai "count_socks".
Tautan dibuat untuk C dan bahasa lain seperti C dengan aturan penamaan simbol sederhana ini. Jadi simbol di linker hanyalah string sederhana.
Tapi di C ++ bahasanya memungkinkan Anda memiliki namespace, dan polimorfisme dan berbagai hal lain yang bertentangan dengan aturan sederhana tersebut. Keenam fungsi polimorfik Anda yang disebut "tambah" harus memiliki simbol yang berbeda, atau simbol yang salah akan digunakan oleh file objek lain. Ini dilakukan dengan "merusak" (itu istilah teknis) nama-nama simbol.
Saat menautkan kode C ++ ke pustaka atau kode C, Anda memerlukan "C" eksternal apa pun yang ditulis dalam C, seperti file header untuk pustaka C, untuk memberi tahu compiler C ++ Anda bahwa nama-nama simbol ini tidak boleh dihancurkan, sementara yang lainnya kode C ++ Anda tentu saja harus rusak atau tidak akan berfungsi.
sumber
Saat Anda menautkan pustaka C ke dalam file objek C ++
C dan C ++ menggunakan skema berbeda untuk penamaan simbol. Ini memberi tahu penaut untuk menggunakan skema C saat menautkan di pustaka yang diberikan.
Menggunakan skema penamaan C memungkinkan Anda mereferensikan simbol gaya C. Jika tidak, linker akan mencoba simbol gaya C ++ yang tidak akan berfungsi.
sumber
Anda harus menggunakan "C" eksternal kapan pun Anda menyertakan fungsi pendefinisian header yang berada dalam file yang dikompilasi oleh kompilator C, yang digunakan dalam file C ++. (Banyak pustaka C standar mungkin menyertakan pemeriksaan ini di header mereka untuk membuatnya lebih sederhana bagi pengembang)
Misalnya, jika Anda memiliki proyek dengan 3 file, util.c, util.h, dan main.cpp dan kedua file .c dan .cpp dikompilasi dengan compiler C ++ (g ++, cc, dll) maka isn ' t benar-benar diperlukan, dan bahkan dapat menyebabkan kesalahan tautan. Jika proses build Anda menggunakan compiler C biasa untuk util.c, Anda perlu menggunakan "C" extern saat menyertakan util.h.
Apa yang terjadi adalah C ++ mengkodekan parameter fungsi dalam namanya. Beginilah cara kerja kelebihan beban fungsi. Semua yang cenderung terjadi pada fungsi C adalah penambahan garis bawah ("_") di awal nama. Tanpa menggunakan "C" eksternal, linker akan mencari fungsi bernama DoSomething @@ int @ float () saat nama sebenarnya dari fungsi tersebut adalah _DoSomething () atau hanya DoSomething ().
Menggunakan extern "C" memecahkan masalah di atas dengan memberi tahu compiler C ++ bahwa ia harus mencari fungsi yang mengikuti konvensi penamaan C, bukan C ++.
sumber
Kompilator C ++ membuat nama simbol yang berbeda dari kompilator C. Jadi, jika Anda mencoba membuat panggilan ke fungsi yang berada dalam file C, yang dikompilasi sebagai kode C, Anda perlu memberi tahu compiler C ++ bahwa nama simbol yang coba diatasi terlihat berbeda dari yang semula; jika tidak, langkah tautan akan gagal.
sumber
The
extern "C" {}
membangun menginstruksikan kompiler untuk tidak melakukan mangling pada nama dinyatakan dalam kurung. Biasanya, kompilator C ++ "meningkatkan" nama fungsi sehingga mereka menyandikan informasi jenis tentang argumen dan nilai yang dikembalikan; ini disebut nama yang hancur . Theextern "C"
membangun mencegah mangling.Ini biasanya digunakan ketika kode C ++ perlu memanggil pustaka bahasa C. Ini juga dapat digunakan saat mengekspos fungsi C ++ (dari DLL, misalnya) ke klien C.
sumber
Ini digunakan untuk menyelesaikan masalah kerusakan nama. extern C berarti bahwa fungsinya berada dalam API gaya-C "datar".
sumber
Dekompilasi
g++
biner yang dihasilkan untuk melihat apa yang sedang terjadiUntuk memahami mengapa
extern
perlu, hal terbaik yang harus dilakukan adalah memahami apa yang terjadi secara detail di file objek dengan contoh:main.cpp
void f() {} void g(); extern "C" { void ef() {} void eg(); } /* Prevent g and eg from being optimized away. */ void h() { g(); eg(); }
Kompilasi dengan keluaran ELF Linux GCC 4.8 :
Dekompilasi tabel simbol:
Outputnya berisi:
Num: Value Size Type Bind Vis Ndx Name 8: 0000000000000000 6 FUNC GLOBAL DEFAULT 1 _Z1fv 9: 0000000000000006 6 FUNC GLOBAL DEFAULT 1 ef 10: 000000000000000c 16 FUNC GLOBAL DEFAULT 1 _Z1hv 11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv 12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg
Penafsiran
Kami melihat bahwa:
ef
daneg
disimpan dalam simbol dengan nama yang sama seperti di kodesimbol-simbol lainnya hancur. Mari kita uraikan:
$ c++filt _Z1fv f() $ c++filt _Z1hv h() $ c++filt _Z1gv g()
Kesimpulan: kedua tipe simbol berikut tidak rusak:
Ndx = UND
), untuk diberikan pada tautan atau waktu proses dari file objek lainJadi, Anda memerlukan
extern "C"
keduanya saat menelepon:g++
untuk mengharapkan simbol tak berubah yang diproduksi olehgcc
g++
untuk menghasilkan simbol tak beraturan untukgcc
digunakanHal-hal yang tidak bekerja di luar C
Jelas bahwa fitur C ++ apa pun yang membutuhkan name mangling tidak akan berfungsi di dalam
extern C
:extern "C" { // Overloading. // error: declaration of C function ‘void f(int)’ conflicts with void f(); void f(int i); // Templates. // error: template with C linkage template <class C> void f(C i) { } }
Minimal runnable C dari contoh C ++
Demi kelengkapan dan untuk newbs di luar sana, lihat juga: Bagaimana menggunakan file sumber C dalam proyek C ++?
Memanggil C dari C ++ cukup mudah: setiap fungsi C hanya memiliki satu kemungkinan simbol yang tidak rusak, jadi tidak diperlukan pekerjaan tambahan.
main.cpp
#include <cassert> #include "c.h" int main() { assert(f() == 1); }
ch
#ifndef C_H #define C_H /* This ifdef allows the header to be used from both C and C++. */ #ifdef __cplusplus extern "C" { #endif int f(); #ifdef __cplusplus } #endif #endif
cc
#include "c.h" int f(void) { return 1; }
Lari:
g++ -c -o main.o -std=c++98 main.cpp gcc -c -o c.o -std=c89 c.c g++ -o main.out main.o c.o ./main.out
Tanpa
extern "C"
tautan gagal dengan:main.cpp:6: undefined reference to `f()'
karena
g++
berharap menemukan yang hancurf
, yanggcc
tidak menghasilkan.Contoh di GitHub .
Minimal C ++ yang dapat dijalankan dari contoh C.
Memanggil C ++ dari agak lebih sulit: kita harus membuat versi non-rusak secara manual dari setiap fungsi yang ingin kita tampilkan.
Di sini kami mengilustrasikan cara mengekspos overload fungsi C ++ ke C.
main.c
#include <assert.h> #include "cpp.h" int main(void) { assert(f_int(1) == 2); assert(f_float(1.0) == 3); return 0; }
cpp.h
#ifndef CPP_H #define CPP_H #ifdef __cplusplus // C cannot see these overloaded prototypes, or else it would get confused. int f(int i); int f(float i); extern "C" { #endif int f_int(int i); int f_float(float i); #ifdef __cplusplus } #endif #endif
cpp.cpp
#include "cpp.h" int f(int i) { return i + 1; } int f(float i) { return i + 2; } int f_int(int i) { return f(i); } int f_float(float i) { return f(i); }
Lari:
gcc -c -o main.o -std=c89 -Wextra main.c g++ -c -o cpp.o -std=c++98 cpp.cpp g++ -o main.out main.o cpp.o ./main.out
Tanpanya
extern "C"
gagal dengan:main.c:6: undefined reference to `f_int' main.c:7: undefined reference to `f_float'
karena
g++
menghasilkan simbol rusak yanggcc
tidak dapat ditemukan.Contoh di GitHub .
Diuji di Ubuntu 18.04.
sumber