Menggabungkan C ++ dan C - bagaimana cara kerja #ifdef __cplusplus?

319

Saya sedang mengerjakan proyek yang memiliki banyak kode C sebelumnya . Kami sudah mulai menulis dalam C ++, dengan tujuan untuk akhirnya mengonversi kode lama juga. Saya agak bingung tentang bagaimana C dan C ++ berinteraksi. Saya mengerti bahwa dengan membungkus kode C dengan extern "C"kompiler C ++ tidak akan memotong-motong nama kode C , tapi saya tidak sepenuhnya yakin bagaimana menerapkannya.

Jadi, di bagian atas setiap file header C (setelah penjaga termasuk), kita miliki

#ifdef __cplusplus
extern "C" {
#endif

dan di bagian bawah, kita menulis

#ifdef __cplusplus
}
#endif

Di antara keduanya, kami memiliki semua prototipe fungsi, typedef, dan fungsi kami. Saya punya beberapa pertanyaan, untuk melihat apakah saya memahami ini dengan benar:

  1. Jika saya memiliki file C ++ A.hh yang mencakup file header C Bh, termasuk file header C lain Ch, bagaimana cara kerjanya? Saya pikir ketika kompiler melangkah ke Bh, __cplusplusakan ditentukan, sehingga akan membungkus kode dengan extern "C" (dan __cplusplustidak akan didefinisikan di dalam blok ini). Jadi, ketika melangkah ke Ch, __cplusplustidak akan ditentukan dan kode tidak akan dibungkus extern "C". Apakah ini benar?

  2. Apakah ada yang salah dengan membungkus sepotong kode extern "C" { extern "C" { .. } }? Apa yang akan extern "C" dilakukan yang kedua ?

  3. Kami tidak meletakkan pembungkus ini di sekitar file .c, hanya file .h. Jadi, apa yang terjadi jika suatu fungsi tidak memiliki prototipe? Apakah kompiler berpikir bahwa itu adalah fungsi C ++?

  4. Kami juga menggunakan beberapa kode pihak ketiga yang ditulis dalam C , dan tidak memiliki pembungkus semacam ini di sekitarnya. Setiap kali saya memasukkan tajuk dari perpustakaan itu, saya telah meletakkan di extern "C"sekitar #include. Apakah ini cara yang tepat untuk menghadapinya?

  5. Akhirnya, apakah ini merupakan ide bagus? Apakah ada hal lain yang harus kita lakukan? Kita akan mencampur C dan C ++ untuk masa yang akan datang, dan saya ingin memastikan bahwa kami mencakup semua basis kami.

dublev
sumber
2
Singkatnya, ini adalah penjelasan terbaik: To ensure that the names declared in that portion of code have C linkage, and thus C++ name mangling is not performed. (Saya mendapatkannya dari tautan )
anhldbk
Anda tidak harus mencantumkan nama bahasa C dalam huruf tebal
Edward Karak

Jawaban:

290

extern "C"tidak benar-benar mengubah cara kompiler membaca kode. Jika kode Anda dalam file .c, itu akan dikompilasi sebagai C, jika itu dalam file .cpp, itu akan dikompilasi sebagai C ++ (kecuali jika Anda melakukan sesuatu yang aneh dengan konfigurasi Anda).

Apa yang extern "C"dilakukan adalah memengaruhi pertautan. Fungsi-fungsi C ++, ketika dikompilasi, memiliki nama-nama mereka yang hancur - inilah yang memungkinkan overloading terjadi. Nama fungsi akan dimodifikasi berdasarkan pada jenis dan jumlah parameter, sehingga dua fungsi dengan nama yang sama akan memiliki nama simbol yang berbeda.

Kode di dalam sebuah extern "C"masih merupakan kode C ++. Ada batasan pada apa yang dapat Anda lakukan di blok "C" eksternal, tetapi semuanya tentang keterkaitan. Anda tidak dapat menentukan simbol baru yang tidak dapat dibangun dengan tautan C. Itu berarti tidak ada kelas atau template, misalnya.

extern "C"blok sarang dengan baik. Ada juga extern "C++"jika Anda menemukan diri Anda benar-benar terjebak di dalam extern "C"wilayah, tetapi itu bukan ide yang bagus dari sudut pandang kebersihan.

Sekarang, khusus tentang pertanyaan bernomor Anda:

Mengenai # 1: __cplusplus akan tetap didefinisikan di dalam extern "C"blok. Ini tidak masalah, karena blok harus bersarang dengan rapi.

Mengenai # 2: __cplusplus akan didefinisikan untuk setiap unit kompilasi yang dijalankan melalui kompiler C ++. Secara umum, itu berarti file .cpp dan semua file yang disertakan oleh file .cpp itu. .H (atau .hh atau .hpp atau what-you-you) yang sama dapat diartikan sebagai C atau C ++ pada waktu yang berbeda, jika unit kompilasi yang berbeda memasukkannya. Jika Anda ingin prototipe dalam file .h merujuk ke nama simbol C, maka mereka harus memilikiextern "C" ketika ditafsirkan sebagai C ++, dan mereka tidak harus extern "C"ketika ditafsirkan sebagai C - maka #ifdef __cpluspluspemeriksaan.

Untuk menjawab pertanyaan Anda # 3: fungsi tanpa prototipe akan memiliki pertautan C ++ jika mereka ada dalam file .cpp dan bukan di dalam suatu extern "C" blok. Ini baik-baik saja, karena, jika tidak memiliki prototipe, itu hanya dapat dipanggil oleh fungsi lain dalam file yang sama, dan kemudian Anda biasanya tidak peduli seperti apa hubungan itu, karena Anda tidak berencana memiliki fungsi itu dipanggil oleh apa pun di luar unit kompilasi yang sama pula.

Untuk # 4, Anda mendapatkannya dengan tepat. Jika Anda menyertakan tajuk untuk kode yang memiliki tautan C (seperti kode yang dikompilasi oleh kompiler C), maka Anda harus extern "C"tajuk - dengan cara itu Anda akan dapat menautkan dengan pustaka. (Kalau tidak, penghubung Anda akan mencari fungsi dengan nama seperti _Z1hicsaat Anda mencarivoid h(int, char)

5: Pencampuran semacam ini adalah alasan umum untuk digunakan extern "C", dan saya tidak melihat ada yang salah dengan melakukannya dengan cara ini - pastikan Anda mengerti apa yang Anda lakukan.

Andrew Shelansky
sumber
10
Baik untuk menyebutkan extern "C++"kapan header / kode C ++ Anda terperangkap jauh di dalam beberapa kode C
deddebme
1
Saya menulis program C sederhana. Di dalamnya saya menambahkan blok #ifdef __cplusplus dan menambahkan printf ("__ cplusplus defined \ n"); di dalamnya. Jika saya mengompilasinya dengan gcc, "__cplusplus defined" tidak dicetak, tetapi jika saya mengompilasinya dengan g ++, itu dicetak. Jadi saya pikir __cplusplus berarti kompiler adalah kompiler C ++ (Anda mengatakannya). Benar kan? (karena saya melihat Anda mengatakan '__cplusplus harus didefinisikan di dalam blok "C" extern'. bisakah kita mendefinisikan __cplusplus secara eksplisit?
Chan Kim
1
Meskipun Anda harus dapat mendefinisikan (hampir) apa pun yang Anda inginkan, intinya __cplusplusadalah untuk menentukan apakah C++sedang digunakan vs C, jadi mendefinisikannya secara manual / secara eksplisit menentang tujuan itu ...
nurchi
Extern "C" memang bukan tentang bagaimana kompiler melihat file sumber, ini semua tentang bagaimana ia melihat file header. struct mungkin memiliki ukuran yang berbeda ketika dikompilasi sebagai C vs C ++, ada nama mangling tentu saja, dan kemungkinan perbedaan lainnya juga.
Nick
39
  1. extern "C"tidak mengubah ada atau tidaknya __cplusplusmakro. Itu hanya mengubah tautan dan susunan nama dari deklarasi yang dibungkus.

  2. Anda dapat membuat sarang extern "C"dengan cukup bahagia.

  3. Jika Anda mengkompilasi .cfile Anda sebagai C ++ maka apa pun yang tidak di extern "C"blok, dan tanpa extern "C"prototipe akan diperlakukan sebagai fungsi C ++. Jika Anda mengkompilasinya sebagai C maka tentu saja semuanya akan menjadi fungsi C.

  4. Iya

  5. Anda dapat dengan aman mencampurkan C dan C ++ dengan cara ini.

Anthony Williams
sumber
Jika Anda mengkompilasi .cfile sebagai C ++, maka semuanya dikompilasi sebagai kode C ++, bahkan jika itu dalam sebuah extern "C"blok. The extern "C"kode tidak dapat menggunakan fitur yang bergantung pada C ++ memanggil konvensi (misalnya operator overloading) tapi tubuh fungsi masih disusun sebagai C ++, dengan semua yang memerlukan.
David C.
21

Sepasang gotcha yang merupakan colloraries untuk jawaban terbaik Andrew Shelansky dan untuk sedikit tidak setuju tidak benar-benar mengubah cara kompiler membaca kode

Karena prototipe fungsi Anda dikompilasi sebagai C, Anda tidak dapat memiliki overloading dari nama fungsi yang sama dengan parameter yang berbeda - itulah salah satu fitur kunci dari pembuatan nama kompiler. Ini dideskripsikan sebagai masalah tautan tetapi itu tidak sepenuhnya benar - Anda akan mendapatkan kesalahan dari penyusun dan tautan.

Kesalahan kompiler akan terjadi jika Anda mencoba menggunakan fitur-fitur C ++ dari deklarasi prototipe seperti overloading.

Kesalahan tautan akan terjadi kemudian karena fungsi Anda tampaknya tidak ditemukan, jika Anda tidak memiliki pembungkus extern "C" di sekitar deklarasi dan header disertakan dalam campuran sumber C dan C ++.

Salah satu alasan untuk mencegah orang menggunakan kompilasi C sebagai pengaturan C ++ adalah karena ini berarti kode sumber mereka tidak lagi portabel. Pengaturan itu adalah pengaturan proyek dan jadi jika file .c jatuh ke proyek lain, itu tidak akan dikompilasi sebagai c ++. Saya lebih suka orang meluangkan waktu untuk mengubah nama sufiks file menjadi .cpp.

Andy Dent
sumber
1
Ini adalah penyebab samar, mencabut rambutku. Benar-benar perlu diposting di suatu tempat.
Mitchell Currie
3

Ini tentang ABI, untuk memungkinkan aplikasi C dan C ++ menggunakan antarmuka C tanpa masalah.

Karena bahasa C sangat mudah, pembuatan kode stabil selama bertahun-tahun untuk berbagai kompiler, seperti GCC, Borland C \ C ++, MSVC dll.

Sementara C ++ menjadi semakin populer, banyak hal yang harus ditambahkan ke dalam domain C ++ baru (misalnya akhirnya Cfront ditinggalkan di AT&T karena C tidak dapat mencakup semua fitur yang dibutuhkan). Seperti fitur templat , dan pembuatan kode waktu kompilasi, dari masa lalu, berbagai vendor kompiler benar-benar melakukan implementasi aktual dari kompiler dan tautan C ++ secara terpisah, ABI yang sebenarnya tidak kompatibel sama sekali dengan program C ++ pada platform yang berbeda.

Orang-orang mungkin masih ingin mengimplementasikan program yang sebenarnya dalam C ++ tetapi tetap menggunakan antarmuka C lama dan ABI seperti biasa, file header harus menyatakan extern "C" {} , ini memberitahu kompiler menghasilkan C ABI yang kompatibel / lama / sederhana / mudah. untuk fungsi antarmuka jika kompiler adalah kompiler C bukan kompiler C ++.

Bo Zhou
sumber