@Litherum Na szczycie mojej głowy mówi kompilatorowi, aby skompilował ten zakres kodu za pomocą C, biorąc pod uwagę, że masz kompilator krzyżowy. Oznacza to również, że masz plik Cpp, w którym masz tę foo()funkcję.
ha9u63ar
1
@ ha9u63ar To „z czubka głowy”. Cała reszta twojego komentarza jest również niepoprawna. Polecam go usunąć.
TamaMcGlinn
Odpowiedzi:
1556
extern „C” sprawia, że nazwa funkcji w C ++ ma powiązanie „C” (kompilator nie zmienia nazwy), dzięki czemu kod klienta C może połączyć się (tj. użyć) z funkcją za pomocą pliku nagłówkowego zgodnego z „C”, który zawiera tylko deklaracja twojej funkcji. Definicja funkcji zawarta jest w formacie binarnym (skompilowanym przez kompilator C ++), do którego linker klienta „C” będzie następnie używał przy użyciu nazwy „C”.
Ponieważ C ++ ma przeciążenie nazw funkcji, a C nie, kompilator C ++ nie może po prostu użyć nazwy funkcji jako unikalnego identyfikatora do połączenia, więc zmienia nazwę, dodając informacje o argumentach. Kompilator prądu przemiennego nie musi zmieniać nazwy, ponieważ nie można przeciążać nazw funkcji w C. Po stwierdzeniu, że funkcja ma zewnętrzne połączenie „C” w języku C ++, kompilator C ++ nie dodaje informacji o typie argumentu / parametru do nazwy używanej dla połączenie.
Abyś wiedział, możesz jawnie określić powiązanie „C” z każdą indywidualną deklaracją / definicją lub użyć bloku, aby pogrupować sekwencję deklaracji / definicji w celu uzyskania określonego powiązania:
Jeśli zależy Ci na technicznych szczegółach, są one wymienione w sekcji 7.5 standardu C ++ 03, oto krótkie streszczenie (z naciskiem na zewnętrzne „C”):
extern „C” jest specyfikacją powiązania
Każdy kompilator jest wymagany do zapewnienia połączenia „C”
specyfikacja powiązania powinna występować tylko w zakresie przestrzeni nazw
wszystkie typy funkcji, nazwy funkcji i nazwy zmiennych mają powiązanie językowe Patrz komentarz Richarda: Tylko nazwy funkcji i nazwy zmiennych z powiązaniem zewnętrznym mają powiązanie językowe
dwa typy funkcji z odrębnymi powiązaniami językowymi są odrębnymi typami, nawet jeśli poza tym są identyczne
gniazdo specyfikacji połączenia, wewnętrzne określa ostateczne połączenie
extern „C” jest ignorowany dla członków klasy
co najwyżej jedna funkcja o określonej nazwie może mieć powiązanie „C” (niezależnie od przestrzeni nazw)
extern „C” wymusza, aby funkcja miała zewnętrzny linking (nie może uczynić go statycznym) Patrz komentarz Richarda: „static” wewnątrz „extern„ C ”” jest poprawny; tak zadeklarowana jednostka ma powiązanie wewnętrzne, a zatem nie ma powiązania językowego
Powiązanie z C ++ z obiektami zdefiniowanymi w innych językach oraz z obiektami zdefiniowanymi w C ++ z innych języków jest zdefiniowane w implementacji i zależy od języka. Tylko wtedy, gdy strategie rozmieszczenia obiektów w dwóch implementacjach językowych są wystarczająco podobne, można uzyskać takie powiązanie
Kompilator języka C nie korzysta z manipulacji, co robi C ++. Jeśli więc chcesz wywołać interfejs ac z programu c ++, musisz wyraźnie zadeklarować, że interfejs c jest „extern c”.
Sam Liao,
58
@Faisal: nie próbuj łączyć kodu zbudowanego z różnymi kompilatorami C ++, nawet jeśli wszystkie odsyłacze to „zewnętrzne” C ”. Często występują różnice między układami klas lub mechanizmami używanymi do obsługi wyjątków lub mechanizmami zapewniającymi, że zmienne są inicjowane przed użyciem, lub inne takie różnice, a ponadto mogą być potrzebne dwie oddzielne biblioteki obsługi środowiska wykonawczego C ++ (jedna dla każdy kompilator).
Jonathan Leffler,
8
„extern„ C ”wymusza, aby funkcja miała zewnętrzny łącznik (nie może spowodować jej statyczności)” jest niepoprawna. poprawne jest „statyczne” wewnątrz „zewnętrznego” „C”; tak zadeklarowana jednostka ma powiązanie wewnętrzne, a zatem nie ma powiązania językowego.
Richard Smith
14
„wszystkie typy funkcji, nazwy funkcji i nazwy zmiennych mają powiązanie językowe” również jest niepoprawne. Tylko nazwy funkcji i nazwy zmiennych z zewnętrznym powiązaniem mają powiązanie językowe.
Richard Smith
9
Zauważ, że extern "C" { int i; }jest to definicja. To może nie być to, co zamierzałeś, obok braku definicji void g(char);. Aby uczynić go niezdefiniowanym, potrzebujesz extern "C" { extern int i; }. Z drugiej strony, składnia z jedną deklaracją bez nawiasów klamrowych sprawia, że deklaracja nie jest definicją: extern "C" int i;jest taka sama jakextern "C" { extern int i; }
aschepler
327
Chciałem tylko dodać trochę informacji, ponieważ jeszcze nie widziałem.
Bardzo często zobaczysz kod w nagłówkach C, takich jak:
#ifdef __cplusplus
extern"C"{#endif// all of your legacy C code here#ifdef __cplusplus
}#endif
Osiąga to to, że pozwala na użycie tego pliku nagłówka C z kodem C ++, ponieważ zostanie zdefiniowane makro „__cplusplus”. Ale można też nadal korzystać z kodu starszego typu C, gdzie makro NIE zdefiniowanym, więc nie będzie widać unikalnie C ++ konstrukt.
Chociaż widziałem również kod C ++, taki jak:
extern"C"{#include"legacy_C_header.h"}
jak sądzę, osiąga to samo.
Nie jestem pewien, który sposób jest lepszy, ale widziałem oba.
Istnieje wyraźna różnica. W przypadku tego pierwszego, jeśli skompilujesz ten plik za pomocą normalnego kompilatora gcc, wygeneruje on obiekt, w którym nazwa funkcji nie jest zniekształcona. Jeśli następnie połączysz obiekty C i C ++ z linkerem, NIE znajdzie on funkcji. Będziesz musiał dołączyć te pliki „starszego nagłówka” do słowa kluczowego extern, tak jak w drugim bloku kodu.
Anne van Rossum
8
@Anne: Kompilator C ++ będzie również szukał niezmienionych nazw, ponieważ zobaczył extern "C"w nagłówku). Działa świetnie, używał tej techniki wiele razy.
Ben Voigt
20
@Anne: To nie w porządku, pierwszy też jest w porządku. Jest ignorowany przez kompilator C i ma taki sam efekt jak drugi w C ++. Kompilatorowi nie zależy na tym, czy napotka extern "C"przed czy po dołączeniu nagłówka. Zanim dotrze do kompilatora, jest to tylko jeden długi strumień wstępnie przetworzonego tekstu.
Ben Voigt
8
@ Anne, nie, myślę, że dotknął cię jakiś inny błąd w źródle, ponieważ to, co opisujesz, jest złe. Żadna wersja g++nie pomyliła tego, dla jakiegokolwiek celu, w dowolnym momencie w ciągu ostatnich 17 lat. Chodzi o to, że w pierwszym przykładzie nie ma znaczenia, czy używasz kompilatora C czy C ++, nie będzie dokonywanych zmian nazw dla nazw w extern "C"bloku.
Jonathan Wakely
7
„który z nich jest lepszy” - z pewnością pierwszy wariant jest lepszy: pozwala na bezpośrednie dołączanie nagłówka, bez żadnych dalszych wymagań, zarówno w kodzie C, jak i C ++. Drugie podejście polega na obejściu nagłówków C, autor zapomniał o strażnikach C ++ (nie ma jednak problemu, jeśli zostaną dodane później, wówczas zagnieżdżone zewnętrzne deklaracje „C” są akceptowane ...).
Aconcagua,
267
Dekompiluj g++wygenerowany plik binarny, aby zobaczyć, co się dzieje
main.cpp
void f(){}void g();extern"C"{void ef(){}void eg();}/* Prevent g and eg from being optimized away. */void h(){ g(); eg();}
8:00000000000000007 FUNC GLOBAL DEFAULT 1 _Z1fv
9:00000000000000077 FUNC GLOBAL DEFAULT 1 ef
10:000000000000000e17 FUNC GLOBAL DEFAULT 1 _Z1hv
11:00000000000000000 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12:00000000000000000 NOTYPE GLOBAL DEFAULT UND _Z1gv
13:00000000000000000 NOTYPE GLOBAL DEFAULT UND eg
Interpretacja
Widzimy to:
efi egbyły przechowywane w symbolach o takiej samej nazwie jak w kodzie
pozostałe symbole były zniekształcone. Odznaczmy je:
Wniosek: oba następujące typy symboli nie zostały zniekształcone:
zdefiniowane
zadeklarowane, ale niezdefiniowane ( Ndx = UND), które należy podać w czasie wykonywania łącza lub uruchomienia z innego pliku obiektowego
Będziesz więc potrzebował extern "C"obu połączeń:
C z C ++: powiedz, g++aby oczekiwał niezmienionych symboli wyprodukowanych przezgcc
C ++ z C: powiedz, g++aby wygenerować nie zniekształcone symbole gccdo użycia
Rzeczy, które nie działają w zewnętrznym C
Staje się oczywiste, że żadna funkcja C ++ wymagająca zmiany nazwy nie będzie działać w środku extern C:
extern"C"{// Overloading.// error: declaration of C function ‘void f(int)’ conflicts withvoid f();void f(int i);// Templates.// error: template with C linkagetemplate<class C>void f(C i){}}
#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"{#endifint f();#ifdef __cplusplus
}#endif#endif
#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"{#endifint 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);}
Najlepsza odpowiedź, ponieważ 1) wyraźnie wspominasz, że extern "C" {pomaga Ci wywoływać niezmienione funkcje C z poziomu programów C ++ , a także niezmodyfikowane funkcje C ++ z poziomu programów C , których inne odpowiedzi nie są tak oczywiste, oraz 2) ponieważ pokazujesz różne przykłady każdy. Dzięki!
Gabriel Staples,
3
Bardzo podoba mi się ta odpowiedź
selfboot
4
Podaj najlepszą odpowiedź, ponieważ pokazuje, jak wywoływać przeciążone funkcje z c
Gaspa79
1
@JaveneCPPMcGowan, co sprawia, że myślisz, że miałem nauczyciela C ++? :-)
Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功
205
W każdym programie C ++ wszystkie funkcje niestatyczne są reprezentowane w pliku binarnym jako symbole. Te symbole to specjalne ciągi tekstowe, które jednoznacznie identyfikują funkcję w programie.
W C nazwa symbolu jest taka sama jak nazwa funkcji. Jest to możliwe, ponieważ w C żadna z dwóch funkcji niestatycznych nie może mieć tej samej nazwy.
Ponieważ C ++ pozwala na przeładowanie i ma wiele funkcji, których C nie lubi - jak klasy, funkcje składowe, specyfikacje wyjątków - nie można po prostu użyć nazwy funkcji jako nazwy symbolu. Aby rozwiązać ten problem, C ++ używa tak zwanego manglingu nazwy, który przekształca nazwę funkcji i wszystkie niezbędne informacje (takie jak liczba i rozmiar argumentów) w dziwnie wyglądający ciąg przetwarzany tylko przez kompilator i linker.
Jeśli więc określisz funkcję, która ma być zewnętrzna C, kompilator nie wykonuje z nią mieszania nazw i można uzyskać do niej bezpośredni dostęp, używając nazwy symbolu jako nazwy funkcji.
Jest to przydatne podczas używania dlsym()i dlopen()do wywoływania takich funkcji.
co rozumiesz przez przydatny? czy nazwa symbolu = nazwa funkcji sprawi, że nazwa symbolu zostanie przekazana do dlsym, czy coś innego?
Błąd
1
@ Błąd: tak. Zasadniczo niemożliwe jest w ogólnym przypadku dlopen () biblioteka współdzielona w C ++, która ma tylko plik nagłówkowy i wybiera odpowiednią funkcję do załadowania. (Na x86 opublikowano specyfikację zmieniającą nazwy w postaci Itanium ABI, którą wszystkie kompilatory x86 używają do zmieniania nazw funkcji C ++, ale nic w tym języku tego nie wymaga.)
Jonathan Tomer
52
C ++ zmienia nazwy funkcji, aby utworzyć język obiektowy z języka proceduralnego
Większość języków programowania nie jest zbudowana na istniejących językach programowania. C ++ jest zbudowany na C, a ponadto jest obiektowym językiem programowania zbudowanym z proceduralnego języka programowania, i dlatego istnieją wyrażenia C ++, extern "C"które zapewniają zgodność wsteczną z C.
Spójrzmy na następujący przykład:
#include<stdio.h>// Two functions are defined with the same name// but have different parametersvoid printMe(int a){
printf("int: %i\n", a);}void printMe(char a){
printf("char: %c\n", a);}int main(){
printMe("a");
printMe(1);return0;}
Kompilator prądu zmiennego nie skompiluje powyższego przykładu, ponieważ ta sama funkcja printMejest zdefiniowana dwukrotnie (mimo że mają inne parametry int aniż char a).
gcc -o printMe printMe.c && ./printMe; 1 błąd. PrintMe jest zdefiniowany więcej niż raz.
Kompilator C ++ skompiluje powyższy przykład. To nie obchodzi, że printMejest zdefiniowane dwa razy.
g ++ -o printMe printMe.c && ./printMe;
Wynika to z faktu, że kompilator C ++ domyślnie zmienia nazwy funkcji ( mangles ) na podstawie ich parametrów. W języku C ta funkcja nie była obsługiwana. Jednak, gdy C ++ został zbudowany nad C, język został zaprojektowany, aby być zorientowanym obiektowo oraz konieczna do utrzymania zdolności do tworzenia różnych klas z metod (funkcji) o tej samej nazwie, a także zastąpić metod ( metoda przesłanianie ) w oparciu o różne parametry
extern "C" mówi „nie zmieniaj nazw funkcji C”
Wyobraźmy sobie jednak, że mamy starszy plik C o nazwie „parent.c”, który includejest nazwami funkcji z innych starszych plików C, „parent.h”, „child.h” itp. Jeśli starszy plik „parent.c” zostanie uruchomiony poprzez kompilator C ++ nazwy funkcji zostaną zniekształcone i nie będą już pasować do nazw funkcji określonych w „parent.h”, „child.h” itd. - więc nazwy funkcji w tych plikach zewnętrznych również będą musiały być zniekształconym. Zarządzanie nazwami funkcji w złożonym programie C, z wieloma zależnościami, może prowadzić do uszkodzenia kodu; więc może być wygodne podanie słowa kluczowego, które może informować kompilator C ++, aby nie zmieniał nazwy funkcji.
extern "C"Kluczowe opowiada kompilator C ++ nazwy nie magiel (zmiana nazwy) C funkcyjnych.
czy nie możemy użyć, extern "C"jeśli mamy tylko dllplik? Mam na myśli, jeśli nie mamy pliku nagłówka, a tylko plik źródłowy (tylko implementacje) i użycie jego funkcji za pomocą wskaźnika funkcji. w tym stanie korzystaliśmy właśnie z funkcji (niezależnie od nazwy).
BattleTested
@tfmontague, dla mnie dobrze to przybiłeś! prosto w głowę.
Artanis Zeratul,
29
Żaden nagłówek C nie może być kompatybilny z C ++ przez zwykłe zawinięcie w zewnętrznym „C”. Gdy identyfikatory w nagłówku C powodują konflikt ze słowami kluczowymi C ++, kompilator C ++ będzie na to narzekał.
Na przykład widziałem, że następujący kod nie działa w g ++:
extern"C"{struct method {intvirtual;};}
Trochę ma to sens, ale należy o tym pamiętać przy przenoszeniu kodu C do C ++.
extern "C"oznacza użycie połączenia C, jak opisano w innych odpowiedziach. Nie oznacza to „kompilacji zawartości jako C” ani nic takiego. int virtual;jest niepoprawny w C ++ i określenie innego powiązania tego nie zmienia.
MM
1
... lub tryb ogólnie, żaden kod zawierający błąd składniowy nie zostanie skompilowany.
Valentin Heinitz,
4
@ValentinHeinitz oczywiście, chociaż użycie „wirtualnego” jako identyfikatora w C nie jest błędem składniowym. Chciałem tylko wskazać, że nie można automatycznie używać żadnego nagłówka C w C ++, umieszczając wokół niego zewnętrzną literę „C”.
Sander Mertens
28
Zmienia powiązanie funkcji w taki sposób, że można wywoływać funkcję z C. W praktyce oznacza to, że nazwa funkcji nie jest zniekształcona .
Mangled jest terminem powszechnie używanym ... Nie sądzę, że kiedykolwiek widziałem „ozdobione” używane w tym znaczeniu.
Matthew Scharley,
1
Microsoft (przynajmniej częściowo) wykorzystuje urządzone zamiast zniekształcone w ich dokumentacji. nawet nadają swojemu narzędziu nazwę, aby cofnąć dekorację undname.
René Nyffenegger
20
Informuje kompilator C ++ o wyszukiwaniu nazw tych funkcji w stylu C podczas łączenia, ponieważ nazwy funkcji skompilowanych w C i C ++ są różne na etapie łączenia.
extern „C” ma być rozpoznawany przez kompilator C ++ i powiadamiać kompilator, że zaznaczona funkcja jest (lub ma być) skompilowana w stylu C. Tak więc podczas łączenia łączy się z poprawną wersją funkcji z C.
Użyłem wcześniej „zewnętrznego” C dla plików dll (biblioteki linków dynamicznych), aby uczynić itd. Funkcję main () „eksportowalną”, aby można ją było później wykorzystać w innym pliku wykonywalnym z biblioteki dll. Może przydałby się przykład tego, gdzie go użyłem.
DLL
#include<string.h>#include<windows.h>usingnamespace 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>usingnamespace std;typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dllFunction mainDLLFunc;//make a variable for function placeholderint main(){char winDir[MAX_PATH];//will hold path of above dllGetCurrentDirectory(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 dllif(DLL==NULL){FreeLibrary((HMODULE)DLL);//if load fails exitreturn0;}
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 nameif(mainDLLFunc==NULL){FreeLibrary((HMODULE)DLL);//if it fails exitreturn0;}
mainDLLFunc();//run exported function FreeLibrary((HMODULE)DLL);}
Podrobiony. extern "C"i __declspec(dllexport)nie są ze sobą powiązane. Pierwsza kontroluje dekorację symbolu, druga odpowiada za utworzenie pozycji eksportu. Możesz również wyeksportować symbol przy użyciu dekoracji nazwy C ++. Poza całkowitym pominięciem tego pytania, w próbie kodu są też inne błędy. Po pierwsze, mainwyeksportowane z biblioteki DLL nie deklaruje wartości zwracanej. Albo zwołanie konwencji. Podczas importowania przypisujesz konwencję losowego wywoływania ( WINAPI) i używasz niewłaściwego symbolu dla kompilacji 32-bitowych (powinno być _mainlub _main@0). Przepraszam, -1.
IInspectable
1
To tylko powtórzyło, że nie wiesz, co robisz, ale robienie tego w ten sposób wydaje się działać dla ciebie, dla jakiejś nieujawnionej listy platform docelowych. Nie rozwiązałeś problemów poruszonych przeze mnie w poprzednim komentarzu. Jest to nadal głos negatywny, ponieważ jest całkowicie błędny (więcej, które nie zmieściłyby się w jednym komentarzu).
Widoczny
1
Opublikowanie odpowiedzi w rodzaju przepełnienia stosu oznacza, że wiesz, co robisz. Jest to oczekiwane. Jeśli chodzi o próbę „zapobiegania uszkodzeniu stosu podczas uruchamiania” : sygnatura funkcji określa zwracaną wartość typu void*, ale implementacja niczego nie zwraca. To będzie latać naprawdę dobrze ...
Widoczny
1
Jeśli wdrożysz coś, co wydaje się działać, na szczęście, to wyraźnie nie wiesz, co robisz (Twoja „działająca” próbka należy do tej kategorii). Jest to niezdefiniowane zachowanie, a pozorne działanie jest prawidłową formą niezdefiniowanego zachowania. Nadal nie jest zdefiniowane. Byłbym bardzo wdzięczny, gdybyś wykazał się większą starannością w przyszłości. Częścią tego może być usunięcie tej proponowanej odpowiedzi.
Widoczny
1
Ponownie interpretujesz funkcję, która niczego nie zwraca, jako funkcję, która zwraca wskaźnik. To szczęście, że x86 bardzo wybacza w odniesieniu do niedopasowanych sygnatur funkcji, a zwłaszcza zwracanych wartości typu całkowego. Twój kod działa tylko przez przypadek. Jeśli się nie zgadzasz, musisz wyjaśnić, dlaczego Twój kod działa niezawodnie.
Widoczny
5
extern "C"to specyfikacja powiązania, która służy do wywoływania funkcji C w plikach źródłowych Cpp . Możemy wywoływać funkcje C, pisać zmienne i dołączać nagłówki . Funkcja jest zadeklarowana w encji zewnętrznej i jest zdefiniowana na zewnątrz. Składnia to
Typ 1:
extern"language"function-prototype
Typ 2:
extern"language"{function-prototype
};
na przykład:
#include<iostream>usingnamespace std;extern"C"{#include<stdio.h>// Include C Headerint n;// Declare a Variablevoid func(int,int);// Declare a function (function prototype)}int main(){
func(int a,int b);// Calling function . . .return0;}// Function definition . . .void func(int m,int n){////}
Ta odpowiedź jest dla niecierpliwych / mają terminy do dotrzymania, poniżej znajduje się tylko część / proste wyjaśnienie:
w C ++ możesz mieć taką samą nazwę w klasie poprzez przeciążenie (na przykład, ponieważ wszystkie są takie same, nie można wyeksportować takiej samej jak z biblioteki DLL itp.) rozwiązaniem tych problemów jest to, że są one konwertowane na różne ciągi znaków (zwane symbolami ), symbole uwzględniają nazwę funkcji, a także argumenty, dzięki czemu każdą z tych funkcji nawet o tej samej nazwie można jednoznacznie zidentyfikować (nazywaną także „przekreślaniem nazwy”)
w C nie ma przeciążenia, nazwa funkcji jest unikalna (więc oddzielny ciąg do jednoznacznej identyfikacji nazwy funkcji nie jest wymagany, więc symbol jest samą nazwą funkcji)
Tak więc
w C ++, przy zmianie nazwy unikalne tożsamości każdej funkcji
w C, nawet bez zmiany nazwy unikalne tożsamości każdej funkcji
Aby zmienić zachowanie C ++, czyli określić, że zmiana nazwy nie powinna mieć miejsca dla określonej funkcji, możesz użyć zewnętrznego „C” przed nazwą funkcji, z jakiegokolwiek powodu, na przykład eksportując funkcję o określonej nazwie z biblioteki dll , do użytku przez jego klientów.
Przeczytaj inne odpowiedzi, aby uzyskać bardziej szczegółowe / poprawne odpowiedzi.
Podczas mieszania C i C ++ (tj. A. Wywołania funkcji C z C ++; oraz b. Wywołania funkcji C ++ z C), mangling nazwy C ++ powoduje problemy z łączeniem. Technicznie rzecz biorąc, ten problem występuje tylko wtedy, gdy funkcje odbiorcy zostały już skompilowane do pliku binarnego (najprawdopodobniej plik biblioteki * .a) przy użyciu odpowiedniego kompilatora.
Musimy więc użyć zewnętrznego „C”, aby wyłączyć mangowanie nazw w C ++.
Witamy w Stack Overflow. Jeśli zdecydujesz się odpowiedzieć na starsze pytanie, które ma dobrze ustalone i poprawne odpowiedzi, dodanie nowej odpowiedzi późno w ciągu dnia może nie przynieść ci uznania. Jeśli masz jakieś nowe, charakterystyczne informacje lub jesteś przekonany, że wszystkie inne odpowiedzi są błędne, dodaj nową odpowiedź, ale „jeszcze jedna odpowiedź”, podając tę samą podstawową informację długo po wygraniu pytania, zwykle wygrywa ” zarobię ci dużo kredytu. Szczerze mówiąc, nie sądzę, żeby w tej odpowiedzi było coś nowego.
Jonathan Leffler
Cóż, powinienem pamiętać o twoim punkcie - w porządku
Susobhan Das
-1
Funkcja void f () skompilowana przez kompilator C i funkcja o tej samej nazwie void f () skompilowana przez kompilator C ++ nie są tą samą funkcją. Jeśli napisałeś tę funkcję w C, a następnie próbowałeś wywołać ją z C ++, to linker szukałby funkcji C ++ i nie znalazłby funkcji C.
extern „C” informuje kompilator C ++, że masz funkcję skompilowaną przez kompilator C. Kiedy powiesz, że został skompilowany przez kompilator C, kompilator C ++ będzie wiedział, jak poprawnie go wywołać.
Pozwala także kompilatorowi C ++ na kompilowanie funkcji C ++ w taki sposób, aby kompilator C mógł ją wywołać. Ta funkcja byłaby oficjalnie funkcją C, ale ponieważ jest kompilowana przez kompilator C ++, może korzystać ze wszystkich funkcji C ++ i ma wszystkie słowa kluczowe C ++.
Kompilator C ++ może kompilować extern "C"funkcję - i (z zastrzeżeniem pewnych ograniczeń) będzie można ją wywoływać za pomocą kodu skompilowanego przez kompilator C.
foo()
funkcję.Odpowiedzi:
extern „C” sprawia, że nazwa funkcji w C ++ ma powiązanie „C” (kompilator nie zmienia nazwy), dzięki czemu kod klienta C może połączyć się (tj. użyć) z funkcją za pomocą pliku nagłówkowego zgodnego z „C”, który zawiera tylko deklaracja twojej funkcji. Definicja funkcji zawarta jest w formacie binarnym (skompilowanym przez kompilator C ++), do którego linker klienta „C” będzie następnie używał przy użyciu nazwy „C”.
Ponieważ C ++ ma przeciążenie nazw funkcji, a C nie, kompilator C ++ nie może po prostu użyć nazwy funkcji jako unikalnego identyfikatora do połączenia, więc zmienia nazwę, dodając informacje o argumentach. Kompilator prądu przemiennego nie musi zmieniać nazwy, ponieważ nie można przeciążać nazw funkcji w C. Po stwierdzeniu, że funkcja ma zewnętrzne połączenie „C” w języku C ++, kompilator C ++ nie dodaje informacji o typie argumentu / parametru do nazwy używanej dla połączenie.
Abyś wiedział, możesz jawnie określić powiązanie „C” z każdą indywidualną deklaracją / definicją lub użyć bloku, aby pogrupować sekwencję deklaracji / definicji w celu uzyskania określonego powiązania:
Jeśli zależy Ci na technicznych szczegółach, są one wymienione w sekcji 7.5 standardu C ++ 03, oto krótkie streszczenie (z naciskiem na zewnętrzne „C”):
wszystkie typy funkcji, nazwy funkcji i nazwy zmiennych mają powiązanie językowePatrz komentarz Richarda: Tylko nazwy funkcji i nazwy zmiennych z powiązaniem zewnętrznym mają powiązanie językoweextern „C” wymusza, aby funkcja miała zewnętrzny linking (nie może uczynić go statycznym)Patrz komentarz Richarda: „static” wewnątrz „extern„ C ”” jest poprawny; tak zadeklarowana jednostka ma powiązanie wewnętrzne, a zatem nie ma powiązania językowegoźródło
extern "C" { int i; }
jest to definicja. To może nie być to, co zamierzałeś, obok braku definicjivoid g(char);
. Aby uczynić go niezdefiniowanym, potrzebujeszextern "C" { extern int i; }
. Z drugiej strony, składnia z jedną deklaracją bez nawiasów klamrowych sprawia, że deklaracja nie jest definicją:extern "C" int i;
jest taka sama jakextern "C" { extern int i; }
Chciałem tylko dodać trochę informacji, ponieważ jeszcze nie widziałem.
Bardzo często zobaczysz kod w nagłówkach C, takich jak:
Osiąga to to, że pozwala na użycie tego pliku nagłówka C z kodem C ++, ponieważ zostanie zdefiniowane makro „__cplusplus”. Ale można też nadal korzystać z kodu starszego typu C, gdzie makro NIE zdefiniowanym, więc nie będzie widać unikalnie C ++ konstrukt.
Chociaż widziałem również kod C ++, taki jak:
jak sądzę, osiąga to samo.
Nie jestem pewien, który sposób jest lepszy, ale widziałem oba.
źródło
extern "C"
w nagłówku). Działa świetnie, używał tej techniki wiele razy.extern "C"
przed czy po dołączeniu nagłówka. Zanim dotrze do kompilatora, jest to tylko jeden długi strumień wstępnie przetworzonego tekstu.g++
nie pomyliła tego, dla jakiegokolwiek celu, w dowolnym momencie w ciągu ostatnich 17 lat. Chodzi o to, że w pierwszym przykładzie nie ma znaczenia, czy używasz kompilatora C czy C ++, nie będzie dokonywanych zmian nazw dla nazw wextern "C"
bloku.Dekompiluj
g++
wygenerowany plik binarny, aby zobaczyć, co się dziejemain.cpp
Skompiluj i zdemontuj wygenerowane wyjście ELF :
Dane wyjściowe zawierają:
Interpretacja
Widzimy to:
ef
ieg
były przechowywane w symbolach o takiej samej nazwie jak w kodziepozostałe symbole były zniekształcone. Odznaczmy je:
Wniosek: oba następujące typy symboli nie zostały zniekształcone:
Ndx = UND
), które należy podać w czasie wykonywania łącza lub uruchomienia z innego pliku obiektowegoBędziesz więc potrzebował
extern "C"
obu połączeń:g++
aby oczekiwał niezmienionych symboli wyprodukowanych przezgcc
g++
aby wygenerować nie zniekształcone symbolegcc
do użyciaRzeczy, które nie działają w zewnętrznym C
Staje się oczywiste, że żadna funkcja C ++ wymagająca zmiany nazwy nie będzie działać w środku
extern C
:Minimalne uruchamialne C z przykładu C ++
Ze względu na kompletność i nowości, zobacz także: Jak korzystać z plików źródłowych C w projekcie C ++?
Wywołanie C z C ++ jest dość łatwe: każda funkcja C ma tylko jeden możliwy niezmieniony symbol, więc nie jest wymagana dodatkowa praca.
main.cpp
ch
cc
Biegać:
Bez
extern "C"
linku nie powiedzie się:ponieważ
g++
oczekuje znalezienia zniekształconegof
, któregogcc
nie wyprodukował.Przykład na GitHub .
Minimalne uruchamialne C ++ z przykładu C.
Wywołanie C ++ z C jest nieco trudniejsze: musimy ręcznie tworzyć nie zniekształcone wersje każdej funkcji, którą chcemy udostępnić.
Tutaj ilustrujemy, jak wystawić przeciążenia funkcji C ++ na C.
main.c
cpp.h
cpp.cpp
Biegać:
Bez
extern "C"
tego zawiedzie:ponieważ
g++
wygenerowane zniekształcone symbole, którychgcc
nie można znaleźć.Przykład na GitHub .
Testowane w Ubuntu 18.04.
źródło
extern "C" {
pomaga Ci wywoływać niezmienione funkcje C z poziomu programów C ++ , a także niezmodyfikowane funkcje C ++ z poziomu programów C , których inne odpowiedzi nie są tak oczywiste, oraz 2) ponieważ pokazujesz różne przykłady każdy. Dzięki!W każdym programie C ++ wszystkie funkcje niestatyczne są reprezentowane w pliku binarnym jako symbole. Te symbole to specjalne ciągi tekstowe, które jednoznacznie identyfikują funkcję w programie.
W C nazwa symbolu jest taka sama jak nazwa funkcji. Jest to możliwe, ponieważ w C żadna z dwóch funkcji niestatycznych nie może mieć tej samej nazwy.
Ponieważ C ++ pozwala na przeładowanie i ma wiele funkcji, których C nie lubi - jak klasy, funkcje składowe, specyfikacje wyjątków - nie można po prostu użyć nazwy funkcji jako nazwy symbolu. Aby rozwiązać ten problem, C ++ używa tak zwanego manglingu nazwy, który przekształca nazwę funkcji i wszystkie niezbędne informacje (takie jak liczba i rozmiar argumentów) w dziwnie wyglądający ciąg przetwarzany tylko przez kompilator i linker.
Jeśli więc określisz funkcję, która ma być zewnętrzna C, kompilator nie wykonuje z nią mieszania nazw i można uzyskać do niej bezpośredni dostęp, używając nazwy symbolu jako nazwy funkcji.
Jest to przydatne podczas używania
dlsym()
idlopen()
do wywoływania takich funkcji.źródło
C ++ zmienia nazwy funkcji, aby utworzyć język obiektowy z języka proceduralnego
Większość języków programowania nie jest zbudowana na istniejących językach programowania. C ++ jest zbudowany na C, a ponadto jest obiektowym językiem programowania zbudowanym z proceduralnego języka programowania, i dlatego istnieją wyrażenia C ++,
extern "C"
które zapewniają zgodność wsteczną z C.Spójrzmy na następujący przykład:
Kompilator prądu zmiennego nie skompiluje powyższego przykładu, ponieważ ta sama funkcja
printMe
jest zdefiniowana dwukrotnie (mimo że mają inne parametryint a
niżchar a
).Kompilator C ++ skompiluje powyższy przykład. To nie obchodzi, że
printMe
jest zdefiniowane dwa razy.Wynika to z faktu, że kompilator C ++ domyślnie zmienia nazwy funkcji ( mangles ) na podstawie ich parametrów. W języku C ta funkcja nie była obsługiwana. Jednak, gdy C ++ został zbudowany nad C, język został zaprojektowany, aby być zorientowanym obiektowo oraz konieczna do utrzymania zdolności do tworzenia różnych klas z metod (funkcji) o tej samej nazwie, a także zastąpić metod ( metoda przesłanianie ) w oparciu o różne parametry
extern "C"
mówi „nie zmieniaj nazw funkcji C”Wyobraźmy sobie jednak, że mamy starszy plik C o nazwie „parent.c”, który
include
jest nazwami funkcji z innych starszych plików C, „parent.h”, „child.h” itp. Jeśli starszy plik „parent.c” zostanie uruchomiony poprzez kompilator C ++ nazwy funkcji zostaną zniekształcone i nie będą już pasować do nazw funkcji określonych w „parent.h”, „child.h” itd. - więc nazwy funkcji w tych plikach zewnętrznych również będą musiały być zniekształconym. Zarządzanie nazwami funkcji w złożonym programie C, z wieloma zależnościami, może prowadzić do uszkodzenia kodu; więc może być wygodne podanie słowa kluczowego, które może informować kompilator C ++, aby nie zmieniał nazwy funkcji.extern "C"
Kluczowe opowiada kompilator C ++ nazwy nie magiel (zmiana nazwy) C funkcyjnych.Na przykład:
extern "C" void printMe(int a);
źródło
extern "C"
jeśli mamy tylkodll
plik? Mam na myśli, jeśli nie mamy pliku nagłówka, a tylko plik źródłowy (tylko implementacje) i użycie jego funkcji za pomocą wskaźnika funkcji. w tym stanie korzystaliśmy właśnie z funkcji (niezależnie od nazwy).Żaden nagłówek C nie może być kompatybilny z C ++ przez zwykłe zawinięcie w zewnętrznym „C”. Gdy identyfikatory w nagłówku C powodują konflikt ze słowami kluczowymi C ++, kompilator C ++ będzie na to narzekał.
Na przykład widziałem, że następujący kod nie działa w g ++:
Trochę ma to sens, ale należy o tym pamiętać przy przenoszeniu kodu C do C ++.
źródło
extern "C"
oznacza użycie połączenia C, jak opisano w innych odpowiedziach. Nie oznacza to „kompilacji zawartości jako C” ani nic takiego.int virtual;
jest niepoprawny w C ++ i określenie innego powiązania tego nie zmienia.Zmienia powiązanie funkcji w taki sposób, że można wywoływać funkcję z C. W praktyce oznacza to, że nazwa funkcji nie jest zniekształcona .
źródło
undname
.Informuje kompilator C ++ o wyszukiwaniu nazw tych funkcji w stylu C podczas łączenia, ponieważ nazwy funkcji skompilowanych w C i C ++ są różne na etapie łączenia.
źródło
extern „C” ma być rozpoznawany przez kompilator C ++ i powiadamiać kompilator, że zaznaczona funkcja jest (lub ma być) skompilowana w stylu C. Tak więc podczas łączenia łączy się z poprawną wersją funkcji z C.
źródło
Użyłem wcześniej „zewnętrznego” C dla plików dll (biblioteki linków dynamicznych), aby uczynić itd. Funkcję main () „eksportowalną”, aby można ją było później wykorzystać w innym pliku wykonywalnym z biblioteki dll. Może przydałby się przykład tego, gdzie go użyłem.
DLL
EXE
źródło
extern "C"
i__declspec(dllexport)
nie są ze sobą powiązane. Pierwsza kontroluje dekorację symbolu, druga odpowiada za utworzenie pozycji eksportu. Możesz również wyeksportować symbol przy użyciu dekoracji nazwy C ++. Poza całkowitym pominięciem tego pytania, w próbie kodu są też inne błędy. Po pierwsze,main
wyeksportowane z biblioteki DLL nie deklaruje wartości zwracanej. Albo zwołanie konwencji. Podczas importowania przypisujesz konwencję losowego wywoływania (WINAPI
) i używasz niewłaściwego symbolu dla kompilacji 32-bitowych (powinno być_main
lub_main@0
). Przepraszam, -1.void*
, ale implementacja niczego nie zwraca. To będzie latać naprawdę dobrze ...extern "C"
to specyfikacja powiązania, która służy do wywoływania funkcji C w plikach źródłowych Cpp . Możemy wywoływać funkcje C, pisać zmienne i dołączać nagłówki . Funkcja jest zadeklarowana w encji zewnętrznej i jest zdefiniowana na zewnątrz. Składnia toTyp 1:
Typ 2:
na przykład:
źródło
Ta odpowiedź jest dla niecierpliwych / mają terminy do dotrzymania, poniżej znajduje się tylko część / proste wyjaśnienie:
Tak więc
w C ++, przy zmianie nazwy unikalne tożsamości każdej funkcji
w C, nawet bez zmiany nazwy unikalne tożsamości każdej funkcji
Aby zmienić zachowanie C ++, czyli określić, że zmiana nazwy nie powinna mieć miejsca dla określonej funkcji, możesz użyć zewnętrznego „C” przed nazwą funkcji, z jakiegokolwiek powodu, na przykład eksportując funkcję o określonej nazwie z biblioteki dll , do użytku przez jego klientów.
Przeczytaj inne odpowiedzi, aby uzyskać bardziej szczegółowe / poprawne odpowiedzi.
źródło
Podczas mieszania C i C ++ (tj. A. Wywołania funkcji C z C ++; oraz b. Wywołania funkcji C ++ z C), mangling nazwy C ++ powoduje problemy z łączeniem. Technicznie rzecz biorąc, ten problem występuje tylko wtedy, gdy funkcje odbiorcy zostały już skompilowane do pliku binarnego (najprawdopodobniej plik biblioteki * .a) przy użyciu odpowiedniego kompilatora.
Musimy więc użyć zewnętrznego „C”, aby wyłączyć mangowanie nazw w C ++.
źródło
Bez sprzeczności z innymi dobrymi odpowiedziami dodam trochę mojego przykładu.
Co dokładnie robi kompilator C ++ : zmienia nazwy w procesie kompilacji, dlatego wymagamy od kompilatora specjalnego traktowania
C
implementacji.Kiedy tworzymy klasy C ++ i dodajemy
extern "C"
, informujemy nasz kompilator C ++, że używamy konwencji wywoływania C.Powód (nazywamy implementację C z C ++): albo chcemy wywołać funkcję C z C ++, albo wywołać funkcję C ++ z C (klasy C ++ ... itd. Nie działają w C).
źródło
Funkcja void f () skompilowana przez kompilator C i funkcja o tej samej nazwie void f () skompilowana przez kompilator C ++ nie są tą samą funkcją. Jeśli napisałeś tę funkcję w C, a następnie próbowałeś wywołać ją z C ++, to linker szukałby funkcji C ++ i nie znalazłby funkcji C.
extern „C” informuje kompilator C ++, że masz funkcję skompilowaną przez kompilator C. Kiedy powiesz, że został skompilowany przez kompilator C, kompilator C ++ będzie wiedział, jak poprawnie go wywołać.
Pozwala także kompilatorowi C ++ na kompilowanie funkcji C ++ w taki sposób, aby kompilator C mógł ją wywołać. Ta funkcja byłaby oficjalnie funkcją C, ale ponieważ jest kompilowana przez kompilator C ++, może korzystać ze wszystkich funkcji C ++ i ma wszystkie słowa kluczowe C ++.
źródło
extern "C"
funkcję - i (z zastrzeżeniem pewnych ograniczeń) będzie można ją wywoływać za pomocą kodu skompilowanego przez kompilator C.