Apa yang sebenarnya dilakukan dengan memasukkan extern "C"
kode C ++?
Sebagai contoh:
extern "C" {
void foo();
}
foo()
fungsi itu.
Apa yang sebenarnya dilakukan dengan memasukkan extern "C"
kode C ++?
Sebagai contoh:
extern "C" {
void foo();
}
foo()
fungsi itu.
Jawaban:
extern "C" membuat nama-fungsi di C ++ memiliki tautan 'C' (kompiler tidak memotong nama) sehingga kode C klien dapat menautkan ke (yaitu menggunakan) fungsi Anda menggunakan file header kompatibel 'C' yang hanya berisi deklarasi fungsi Anda. Definisi fungsi Anda terkandung dalam format biner (yang dikompilasi oleh kompiler C ++ Anda) yang kemudian akan ditautkan oleh penghubung 'C' klien dengan 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, jadi itu mengacaukan nama dengan menambahkan informasi tentang argumen. Kompiler AC tidak perlu memotong-motong nama karena Anda tidak dapat membebani nama fungsi dalam C. Ketika Anda menyatakan bahwa suatu fungsi memiliki hubungan "C" eksternal di C ++, kompiler C ++ tidak menambahkan informasi tipe argumen / parameter ke nama yang digunakan untuk keterkaitan.
Asal tahu saja, Anda dapat menentukan tautan "C" ke setiap pernyataan / definisi individu 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 teknisnya, mereka terdaftar di bagian 7.5 dari standar C ++ 03, di sini adalah ringkasan singkat (dengan penekanan pada extern "C"):
extern "C" { int i; }
adalah definisi. Ini mungkin bukan yang Anda maksudkan, di sebelah non-definisi void g(char);
. Untuk menjadikannya non-definisi, Anda perlu extern "C" { extern int i; }
. Di sisi lain, sintaksis satu deklarasi tanpa kawat gigi membuat deklarasi itu bukan definisi: extern "C" int i;
sama denganextern "C" { extern int i; }
Hanya ingin menambahkan sedikit info, karena saya belum melihatnya diposting.
Anda akan sering melihat kode dalam header C seperti:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
Apa yang dicapai ini adalah memungkinkan Anda untuk menggunakan file header C dengan kode C ++ Anda, karena makro "__cplusplus" akan ditentukan. Tetapi Anda juga masih dapat menggunakannya dengan kode C lawas, di mana makro TIDAK didefinisikan, sehingga tidak akan melihat konstruk unik C ++.
Meskipun, saya juga melihat kode C ++ seperti:
extern "C" {
#include "legacy_C_header.h"
}
yang saya bayangkan mencapai hal yang sama.
Tidak yakin jalan mana yang lebih baik, tetapi saya telah melihat keduanya.
extern "C"
di header). Ini bekerja dengan baik, menggunakan teknik ini berkali-kali.
extern "C"
sebelum atau sesudahnya termasuk header. Pada saat ia mencapai kompiler, itu hanya satu aliran panjang teks yang telah diproses.
g++
salah tentang ini, untuk target apa pun, setidaknya dalam 17 tahun terakhir. Inti dari contoh pertama adalah bahwa tidak masalah apakah Anda menggunakan kompiler C atau C ++, tidak ada nama mangling akan dilakukan untuk nama-nama di extern "C"
blok.
Dekompilasi g++
biner yang dihasilkan untuk melihat apa yang sedang terjadi
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 dan bongkar output ELF yang dihasilkan:
g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
Outputnya berisi:
8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg
Penafsiran
Kami melihat bahwa:
ef
dan eg
disimpan dalam simbol dengan nama yang sama seperti dalam kode
simbol-simbol lainnya hancur. Mari kita pisahkan mereka:
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
Kesimpulan: kedua jenis simbol berikut ini tidak hancur:
Ndx = UND
), harus disediakan pada tautan atau waktu berjalan dari file objek lainJadi, Anda akan membutuhkan extern "C"
keduanya saat menelepon:
g++
untuk mengharapkan simbol tidak cacat yang dihasilkan olehgcc
g++
untuk menghasilkan simbol yang tidak bisa diubah untuk gcc
digunakanHal-hal yang tidak bekerja di luar C
Menjadi jelas bahwa setiap fitur C ++ yang memerlukan nama 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 newb di luar sana, lihat juga: Bagaimana cara menggunakan file sumber C dalam proyek C ++?
Memanggil C dari C ++ cukup mudah: setiap fungsi C hanya memiliki satu simbol yang tidak cacat, jadi tidak diperlukan kerja ekstra.
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++
* because C does not know what this extern "C" thing is. */
#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++
mengharapkan untuk menemukan hancur f
, yang gcc
tidak menghasilkan.
Minimal runnable C ++ dari contoh C
Memanggil C ++ dari C sedikit lebih sulit: kita harus secara manual membuat versi yang tidak cacat dari setiap fungsi yang ingin kita tampilkan.
Di sini kita menggambarkan bagaimana mengekspos kelebihan 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
Tanpa extern "C"
itu gagal dengan:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
karena g++
dihasilkan simbol hancur yang gcc
tidak dapat ditemukan.
Diuji di Ubuntu 18.04.
extern "C" {
membantu Anda memanggil fungsi C yang tidak berubah dari dalam program C ++ , serta fungsi C ++ yang tidak berubah dari dalam program C , yang jawaban lainnya tidak membuatnya begitu jelas, dan 2) karena Anda menunjukkan contoh berbeda dari setiap. Terima kasih!
Dalam setiap program C ++, semua fungsi non-statis direpresentasikan dalam file biner sebagai simbol. Simbol-simbol ini adalah string teks khusus yang secara unik mengidentifikasi fungsi dalam program.
Di C, nama simbol sama dengan nama fungsi. Ini dimungkinkan karena dalam C tidak ada dua fungsi non-statis yang memiliki nama yang sama.
Karena C ++ memungkinkan overloading dan memiliki banyak fitur yang tidak disukai C - seperti kelas, fungsi anggota, spesifikasi pengecualian - tidak mungkin hanya menggunakan nama fungsi sebagai nama simbol. Untuk mengatasinya, C ++ menggunakan apa yang disebut dengan nama mangling, yang mengubah nama fungsi dan semua informasi yang diperlukan (seperti jumlah dan ukuran argumen) menjadi beberapa string yang tampak aneh yang hanya diproses oleh kompiler dan penghubung.
Jadi, jika Anda menentukan fungsi yang akan eksternal C, kompiler tidak melakukan mangling nama dengannya dan dapat langsung diakses menggunakan nama simbolnya sebagai nama fungsi.
Ini berguna saat menggunakan dlsym()
dan dlopen()
untuk memanggil fungsi-fungsi tersebut.
Sebagian besar bahasa pemrograman tidak dibangun di atas bahasa pemrograman yang ada. C ++ dibangun di atas C, dan lebih jauh itu adalah bahasa pemrograman berorientasi objek yang dibangun dari bahasa pemrograman prosedural, dan untuk alasan itu ada ekspresi C ++ seperti extern "C"
yang memberikan 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;
}
Kompiler AC tidak akan mengkompilasi contoh di atas, karena fungsi yang sama printMe
didefinisikan dua kali (walaupun mereka memiliki parameter berbeda int a
vs char a
).
gcc -o printMe printMe.c && ./printMe;
1 kesalahan. PrintMe didefinisikan lebih dari sekali.
Kompiler C ++ akan mengkompilasi contoh di atas. Itu tidak peduli yang printMe
didefinisikan dua kali.
g ++ -o printMe printMe.c && ./printMe;
Ini karena kompiler C ++ secara implisit mengganti nama fungsi ( mangles ) berdasarkan parameternya. Di C, fitur ini tidak didukung. Namun, ketika C ++ dibangun di atas C, bahasa dirancang untuk berorientasi objek, dan diperlukan untuk mendukung kemampuan untuk membuat kelas yang berbeda dengan metode (fungsi) dengan nama yang sama, dan untuk menimpa metode ( metode override ) berdasarkan perbedaan parameter.
extern "C"
mengatakan "jangan membuat nama fungsi C"Namun, bayangkan kita memiliki file legacy C yang bernama "parent.c" yang include
berfungsi dari file legacy C lainnya, "parent.h", "child.h", dll. Jika file legacy "parent.c" dijalankan melalui kompiler C ++, maka nama-nama fungsi akan hancur, dan mereka tidak akan lagi cocok dengan nama-nama fungsi yang ditentukan dalam "parent.h", "child.h", dll - jadi nama-nama fungsi dalam file-file eksternal juga perlu hancur Mengelompokkan nama fungsi di seluruh program C yang kompleks, yang memiliki banyak dependensi, dapat menyebabkan kode rusak; jadi mungkin lebih mudah untuk memberikan kata kunci yang dapat memberitahu kompiler C ++ untuk tidak membuat nama fungsi.
Kata extern "C"
kunci memberi tahu kompiler C ++ untuk tidak memotong-motong (mengganti nama) nama fungsi C.
Sebagai contoh:
extern "C" void printMe(int a);
extern "C"
jika kita hanya memiliki dll
file? Maksud saya jika kita tidak memiliki file header dan hanya memiliki file sumber (hanya implementasi) dan penggunaan fungsinya melalui fungsi pointer. dalam kondisi ini, kami baru saja menggunakan fungsi (terlepas dari namanya).
Tidak ada header-C yang dapat dibuat kompatibel dengan C ++ dengan hanya membungkus extern "C". Ketika pengidentifikasi dalam konflik header-C dengan kata kunci C ++, 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 sesuatu yang perlu diingat ketika porting C-code ke C ++.
extern "C"
berarti menggunakan tautan C, seperti dijelaskan oleh jawaban lain. Itu tidak berarti "mengkompilasi konten sebagai C" atau apa pun. int virtual;
tidak valid dalam C ++ dan menetapkan tautan yang berbeda tidak mengubah itu.
Ini mengubah hubungan fungsi sedemikian rupa sehingga fungsi dapat dipanggil dari C. Dalam prakteknya itu berarti bahwa nama fungsi tidak hancur .
undname
.
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 menghubungkan.
Saya menggunakan 'extern "C"' sebelumnya untuk file dll (dynamic link library) untuk membuat dll. Main () berfungsi "dapat diekspor" sehingga dapat digunakan nanti di executable lain dari dll. Mungkin contoh tempat saya dulu menggunakannya bisa bermanfaat.
DLL
#include <string.h>
#include <windows.h>
using namespace std;
#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}
EXE
#include <string.h>
#include <windows.h>
using namespace std;
typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder
int main()
{
char winDir[MAX_PATH];//will hold path of above dll
GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
strcat(winDir,"\\exmple.dll");//concentrate dll name with path
HINSTANCE DLL = LoadLibrary(winDir);//load example dll
if(DLL==NULL)
{
FreeLibrary((HMODULE)DLL);//if load fails exit
return 0;
}
mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
//defined variable is used to assign a function from dll
//GetProcAddress is used to locate function with pre defined extern name "DLL"
//and matcing function name
if(mainDLLFunc==NULL)
{
FreeLibrary((HMODULE)DLL);//if it fails exit
return 0;
}
mainDLLFunc();//run exported function
FreeLibrary((HMODULE)DLL);
}
extern "C"
dan __declspec(dllexport)
tidak terkait. Dekorasi simbol kontrol sebelumnya, yang terakhir bertanggung jawab untuk membuat entri ekspor. Anda dapat mengekspor simbol menggunakan dekorasi nama C ++ juga. Selain benar-benar kehilangan inti pertanyaan ini, ada juga kesalahan lain dalam sampel kode. Untuk satu, main
diekspor dari DLL Anda tidak menyatakan nilai balik. Atau menelepon konvensi, dalam hal ini. Saat mengimpor, Anda mengaitkan konvensi panggilan acak ( WINAPI
), dan menggunakan simbol yang salah untuk build 32-bit (seharusnya _main
atau _main@0
). Maaf, -1.
void*
, tetapi implementasi Anda tidak mengembalikan apa pun. Itu akan terbang sangat baik ...
extern "C"
adalah spesifikasi tautan yang digunakan untuk memanggil fungsi C dalam file sumber Cpp . Kita dapat memanggil fungsi C, menulis Variabel, & menyertakan tajuk . Fungsi dinyatakan dalam entitas eksternal & itu didefinisikan di luar. Sintaksnya adalah
Tipe 1:
extern "language" function-prototype
Tipe 2:
extern "language"
{
function-prototype
};
misalnya:
#include<iostream>
using namespace std;
extern "C"
{
#include<stdio.h> // Include C Header
int n; // Declare a Variable
void func(int,int); // Declare a function (function prototype)
}
int main()
{
func(int a, int b); // Calling function . . .
return 0;
}
// Function definition . . .
void func(int m, int n)
{
//
//
}
Jawaban ini untuk tenggat waktu yang tidak sabar / harus dipenuhi, hanya sebagian / penjelasan sederhana di bawah ini:
Jadi
di C ++, dengan nama mangling identitas unik setiap fungsi
di C, bahkan tanpa nama mangling identitas unik setiap fungsi
Untuk mengubah perilaku C ++, yaitu, untuk menentukan bahwa nama mangling tidak boleh terjadi untuk fungsi tertentu, Anda dapat menggunakan extern "C" sebelum nama fungsi, untuk alasan apa pun, seperti mengekspor fungsi dengan nama tertentu dari dll. , untuk digunakan oleh kliennya.
Baca jawaban lain, untuk jawaban yang lebih detail / lebih benar.
Saat mencampurkan C dan C ++ (mis., Memanggil fungsi C dari C ++; dan b. Memanggil fungsi C ++ dari C), susunan nama C ++ menyebabkan masalah penghubungan. Secara teknis, masalah ini terjadi hanya ketika fungsi callee telah dikompilasi menjadi biner (kemungkinan besar, file * .a library) menggunakan kompiler yang sesuai.
Jadi kita perlu menggunakan extern "C" untuk menonaktifkan nama mangling di C ++.
Tanpa bertentangan dengan jawaban bagus lainnya, saya akan menambahkan sedikit contoh saya.
Apa sebenarnya yang dilakukan oleh C ++ Compiler : ia membuat nama-nama dalam proses kompilasi, oleh karena itu kami perlu memberi tahu kompiler untuk memperlakukan C
implementasi secara khusus.
Saat kami membuat kelas C ++ dan menambahkan extern "C"
, kami memberi tahu kompiler C ++ kami bahwa kami menggunakan konvensi pemanggilan C.
Alasan (kami memanggil implementasi C dari C ++): kami ingin memanggil fungsi C dari C ++ atau memanggil fungsi C ++ dari C (kelas C ++ ... dll tidak bekerja di C).
Fungsi void f () dikompilasi oleh kompiler C dan fungsi dengan nama yang sama void f () dikompilasi oleh kompiler C ++ bukan fungsi yang sama. Jika Anda menulis fungsi itu dalam C, dan kemudian Anda mencoba memanggilnya dari C ++, maka linker akan mencari fungsi C ++ dan tidak menemukan fungsi C.
extern "C" memberi tahu kompiler C ++ bahwa Anda memiliki fungsi yang dikompilasi oleh kompiler C. Setelah Anda mengatakan bahwa itu dikompilasi oleh kompiler C, kompiler C ++ akan tahu bagaimana memanggilnya dengan benar.
Ini juga memungkinkan kompiler C ++ untuk mengkompilasi fungsi C ++ sedemikian rupa sehingga kompiler C dapat memanggilnya. Fungsi itu secara resmi akan menjadi fungsi C, tetapi karena dikompilasi oleh kompiler C ++, ia dapat menggunakan semua fitur C ++ dan memiliki semua kata kunci C ++.
extern "C"
fungsi - dan (tergantung pada beberapa batasan), ia akan dapat dipanggil dengan kode yang dikompilasi oleh kompiler C.