Pertanyaan Apa efek dari ekstern "C" dalam C ++?


Apa yang sebenarnya terjadi extern "C" ke dalam kode C ++ lakukan?

Sebagai contoh:

extern "C" {
   void foo();
}

1214
2018-06-25 02:10


asal


Jawaban:


Eksternal "C" membuat nama fungsi di C + + memiliki 'C' linkage (kompilator tidak memotong-motong nama) sehingga klien kode C dapat menautkan (yaitu menggunakan) fungsi Anda menggunakan file header kompatibel 'C' yang berisi hanya deklarasi fungsi Anda. Definisi fungsi Anda terkandung dalam format biner (yang dikompilasi oleh kompiler C ++ Anda) bahwa tautan 'C' klien akan menautkan ke menggunakan nama 'C'.

Karena C ++ memiliki overloading nama fungsi dan C tidak, kompiler C ++ tidak bisa hanya menggunakan nama fungsi sebagai id unik untuk ditautkan, sehingga merusak nama dengan menambahkan informasi tentang argumen. Kompilator AC tidak perlu mencopot nama karena Anda tidak dapat membebani nama fungsi di C. Saat Anda menyatakan bahwa suatu fungsi memiliki hubungan eksternal "C" dalam C ++, kompiler C ++ tidak menambahkan informasi jenis argumen / parameter ke nama yang digunakan untuk hubungan.

Asal tahu saja, Anda dapat menentukan hubungan "C" untuk setiap deklarasi / definisi individual secara eksplisit atau menggunakan blok untuk mengelompokkan urutan deklarasi / definisi untuk memiliki tautan tertentu:

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

Jika Anda peduli tentang teknis, mereka tercantum dalam bagian 7.5 dari standar C ++ 03, di sini adalah ringkasan singkat (dengan penekanan pada ekstern "C"):

  • Eksternal "C" adalah spesifikasi hubungan
  • Setiap kompilator wajib untuk menyediakan "C" hubungan
  • spesifikasi tautan akan muncul hanya dalam ruang lingkup namespace
  •  semua jenis fungsi, nama fungsi dan nama variabel memiliki hubungan bahasa   Lihat Komentar Richard: Hanya nama fungsi dan nama variabel dengan tautan eksternal yang memiliki hubungan bahasa
  • dua tipe fungsi dengan keterkaitan bahasa yang berbeda adalah tipe-tipe yang berbeda bahkan jika sebaliknya identik
  • linkage spesifikasi sarang, bagian dalam menentukan hubungan akhir
  • extern "C" diabaikan untuk anggota kelas
  • paling banyak satu fungsi dengan nama tertentu dapat memiliki "C" linkage (terlepas dari namespace)
  •  Eksternal "C" memaksa suatu fungsi untuk memiliki hubungan eksternal (tidak dapat membuatnya statis)    Lihat komentar Richard:    'statis' di dalam 'eksternal' C '' adalah sah; suatu entitas yang dinyatakan memiliki hubungan internal, dan karenanya tidak memiliki hubungan bahasa
  • Kaitan dari C ++ ke objek yang didefinisikan dalam bahasa lain dan ke objek yang didefinisikan dalam C ++ dari bahasa lain adalah yang ditentukan oleh implementasi dan bergantung pada bahasa. Hanya di mana strategi tata letak objek dari dua implementasi bahasa cukup mirip, hubungan seperti itu dapat dicapai

1216
2018-06-25 02:12



Hanya ingin menambahkan sedikit info, karena saya belum melihatnya diposting.

Anda akan sering melihat kode dalam header C seperti ini:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Apa yang dilakukan ini adalah memungkinkan Anda untuk menggunakan file header C dengan kode C ++ Anda, karena makro "__cplusplus" akan ditentukan. Tapi kamu bisa juga masih menggunakannya dengan kode C warisan Anda, di mana makro tersebut TIDAK didefinisikan, sehingga tidak akan melihat konstruk C ++ yang unik.

Meskipun, saya juga melihat kode C ++ seperti:

extern "C" {
#include "legacy_C_header.h"
}

yang saya bayangkan menyelesaikan banyak hal yang sama.

Tidak yakin jalan mana yang lebih baik, tetapi saya telah melihat keduanya.


242
2017-10-21 01:08



Dalam setiap program C ++, semua fungsi non-statis ditampilkan dalam file biner sebagai simbol. Simbol-simbol ini adalah string teks khusus yang secara unik mengidentifikasi fungsi dalam program.

Dalam C, nama simbol sama dengan nama fungsi. Hal ini dimungkinkan karena dalam C tidak ada dua fungsi non-statis dapat memiliki nama yang sama.

Karena C ++ memungkinkan overloading dan memiliki banyak fitur yang tidak C - seperti kelas, fungsi anggota, spesifikasi pengecualian - tidak mungkin hanya menggunakan nama fungsi sebagai nama simbol. Untuk mengatasi itu, C + + menggunakan apa yang disebut nama mangling, yang mengubah nama fungsi dan semua informasi yang diperlukan (seperti jumlah dan ukuran argumen) ke dalam beberapa string aneh yang hanya diproses oleh compiler dan linker.

Jadi, jika Anda menetapkan fungsi untuk menjadi eksternal C, compiler tidak melakukan nama mangling dengan itu dan itu bisa langsung diakses menggunakan nama simbolnya sebagai nama fungsi.

Ini berguna saat digunakan dlsym() dan dlopen() untuk memanggil fungsi-fungsi tersebut.


168
2018-06-25 05:22



Mari kita mendekompilasi file objek g ++ yang dihasilkan untuk melihat apa yang terjadi di dalam implementasi ini.

Hasilkan contoh

Memasukkan:

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

Kompilasi dengan output GCC 4.8 Linux ELF:

g++ -c a.cpp

Dekompilasi tabel simbol:

readelf -s a.o

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

Interpretasi

Kami melihat bahwa:

  • ef dan eg disimpan dalam simbol dengan nama yang sama seperti pada kode

  • simbol-simbol lainnya hancur. Mari kita batalkan mereka:

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()
    

Kesimpulan: kedua jenis lambang berikut adalah tidak kompong:

  • didefinisikan
  • dideklarasikan tetapi tidak terdefinisi (Ndx = UND), yang akan diberikan pada waktu tautan atau waktu dari file objek lain

Jadi Anda perlu extern "C" keduanya saat menelepon:

  • C dari C ++: kirim g++ untuk mengharapkan simbol tak berbentuk yang dihasilkan oleh gcc
  • C ++ dari C: kirim g++ untuk menghasilkan simbol yang tidak diubah untuk gcc menggunakan

Hal-hal yang tidak berhasil di luar C

Jelas bahwa setiap fitur C ++ yang membutuhkan pengamplasan nama tidak akan terisi 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) { }
}

120
2018-05-29 10:06



C ++ mangles function names untuk membuat bahasa berorientasi objek dari bahasa prosedural

Sebagian besar bahasa pemrograman tidak dibangun di atas bahasa pemrograman yang ada. C ++ dibangun di atas C, dan lebih jauh lagi itu adalah bahasa pemrograman berorientasi objek yang dibangun dari bahasa pemrograman prosedural, dan untuk alasan itu ada kata kunci C ++ seperti extern yang menyediakan kompatibilitas ke belakang dengan C.

Mari kita lihat contoh berikut:

#include <stdio.h>

// Two functions are defined with the same name
// but have different parameters

void printMe(int a) {
  printf("int: %i\n", a);
}

void printMe(char a) {
  printf("char: %c\n", a);
}

int main() {
  printMe("a");
  printMe(1);
  return 0;
}

Compiler C tidak akan mengkompilasi contoh di atas, karena fungsi yang sama printMe didefinisikan dua kali (meskipun mereka memiliki parameter yang berbeda int a vs char a).

gcc -o printMe printMe.c && ./printMe;
1 kesalahan. PrintMe didefinisikan lebih dari satu kali.

Compiler C ++ akan mengkompilasi contoh di atas. Itu tidak peduli itu printMe didefinisikan dua kali.

g ++ -o printMe printMe.c && ./printMe;

Ini karena kompiler C ++ secara implisit mengganti nama (mangles) fungsi berdasarkan parameternya. Di C, fitur ini tidak didukung. Namun, ketika C ++ dibangun di atas C, bahasa itu dirancang untuk berorientasi objek, dan diperlukan untuk mendukung kemampuan untuk membuat kelas yang berbeda dengan metode (fungsi) dengan nama yang sama, dan untuk mengganti metode (metode override) berdasarkan parameter yang berbeda.

Extern mengatakan "jangan hilangkan nama fungsi"

Namun, bayangkan kami memiliki file C warisan bernama "parent.c" itu includeNama fungsi dari file legacy C lainnya, "parent.h", "child.h", dll. Jika file "parent.c" legacy dijalankan melalui compiler C ++, maka nama fungsi akan dihapus, dan mereka akan tidak lagi cocok dengan nama fungsi yang ditentukan dalam "parent.h", "child.h", dll - sehingga nama fungsi dalam file eksternal tersebut harus dihapus juga. Dan ini bisa menjadi sangat berantakan. Jadi mungkin nyaman untuk menyediakan kata kunci yang dapat memberitahu compiler C ++ untuk tidak menciutkan nama fungsi.

Itu extern kata kunci memerintahkan kompiler C ++ untuk tidak menghapus nama fungsi (nama). Contoh penggunaan: extern void printMe(int a);


23
2018-02-12 01:50



Ini mengubah keterkaitan fungsi sedemikian rupa sehingga fungsinya dapat dipanggil dari C. Dalam prakteknya itu berarti bahwa nama fungsi tidak kompong.


22
2018-06-25 02:12



Tidak ada C-header yang akan dikompilasi dengan ekstern "C". Ketika pengidentifikasi dalam konflik C-header dengan C ++ kata kunci kompiler C ++ akan mengeluh tentang hal ini.

Sebagai contoh, saya telah melihat kode berikut gagal dalam g ++:

extern "C" {
struct method {
    int virtual;
};
}

Agak masuk akal, tetapi adalah sesuatu yang perlu diingat ketika porting C-code ke C ++.


21
2018-01-09 22:16



Ini menginformasikan kompiler C ++ untuk mencari nama-nama fungsi-fungsi dalam gaya C ketika menghubungkan, karena nama-nama fungsi yang dikompilasi dalam C dan C ++ berbeda selama tahap penautan.


16
2018-06-25 02:12



Eksternal "C" dimaksudkan untuk dikenali oleh kompiler C ++ dan untuk memberitahu compiler bahwa fungsi yang dicatat adalah (atau akan) dikompilasi dalam gaya C. Sehingga saat menautkan, tautan ke versi yang benar dari fungsi C.


13
2018-04-10 09:46