C i C ++ są z pozoru podobne, ale każdy z nich kompiluje się do zupełnie innego zestawu kodu. Gdy dołączasz plik nagłówkowy do kompilatora C ++, kompilator oczekuje kodu C ++. Jeśli jednak jest to nagłówek C, kompilator oczekuje, że dane zawarte w pliku nagłówkowym zostaną skompilowane do określonego formatu - C ++ „ABI” lub „Application Binary Interface”, więc konsolidator się dławi. Jest to preferowane rozwiązanie niż przekazywanie danych C ++ do funkcji oczekującej danych w C.
(Aby dostać się do naprawdę drobiazgów, ABI w C ++ ogólnie `` zmienia '' nazwy swoich funkcji / metod, więc wywołując printf()bez oznaczania prototypu jako funkcji C, C ++ faktycznie wygeneruje wywołanie kodu _Zprintf, plus dodatkowe bzdury na końcu. )
A więc: użyj, extern "C" {...}gdy dołączasz nagłówek ac - to takie proste. W przeciwnym razie wystąpi niezgodność w skompilowanym kodzie i konsolidator się dusi. Jednak w przypadku większości nagłówków nie będziesz ich nawet potrzebować, externponieważ większość nagłówków systemowych C będzie już uwzględniać fakt, że mogą być zawarte w kodzie C ++ i już w externswoim kodzie.
Czy mógłbyś szerzej rozwinąć temat „większość nagłówków systemowych C będzie już uwzględniać fakt, że mogą być one zawarte w kodzie C ++ i już externują swój kod”. ?
Bulat M.
7
@BulatM. Zawierają coś takiego: #ifdef __cplusplus extern "C" { #endif Więc gdy są dołączone z pliku C ++, nadal są traktowane jako nagłówek C.
Calmarius
111
extern "C" określa, jak należy nazywać symbole w generowanym pliku obiektowym. Jeśli funkcja jest zadeklarowana bez zewnętrznego „C”, nazwa symbolu w pliku obiektowym będzie używać zamiany nazw C ++. Oto przykład.
Podany test C tak:
void foo(){}
Kompilowanie i wyświetlanie symboli w pliku obiektowym daje:
$ g++-c test.C
$ nm test.o
0000000000000000 T _Z3foov
U __gxx_personality_v0
W rzeczywistości funkcja foo nazywa się „_Z3foov”. Ten ciąg zawiera między innymi informacje o typie zwracanego typu i parametrach. Jeśli zamiast tego napiszesz test.C w ten sposób:
extern"C"{void foo(){}}
Następnie skompiluj i spójrz na symbole:
$ g++-c test.C
$ nm test.o
U __gxx_personality_v0
0000000000000000 T foo
Otrzymujesz połączenie C. Nazwa funkcji „foo” w pliku obiektowym to po prostu „foo” i nie zawiera ona wszystkich wyszukanych informacji o typie, które pochodzą z zamiany nazw.
Zwykle umieszczasz nagłówek w extern „C” {}, jeśli kod, który jest z nim związany, został skompilowany za pomocą kompilatora C, ale próbujesz wywołać go z C ++. Kiedy to zrobisz, mówisz kompilatorowi, że wszystkie deklaracje w nagłówku będą używać połączenia C. Kiedy połączysz swój kod, twoje pliki .o będą zawierały odniesienia do „foo”, a nie „_Z3fooblah”, co, miejmy nadzieję, pasuje do wszystkiego, co jest w bibliotece, do której tworzysz łącze.
Większość współczesnych bibliotek umieszcza osłony wokół takich nagłówków, aby symbole były deklarowane z odpowiednim połączeniem. np. w wielu standardowych nagłówkach znajdziesz:
Daje to pewność, że gdy kod C ++ zawiera nagłówek, symbole w pliku obiektowym są zgodne z tym, co jest w bibliotece C. Powinieneś tylko umieścić extern "C" {} wokół swojego nagłówka C, jeśli jest stary i nie ma już tych zabezpieczeń.
W C ++ możesz mieć różne encje, które mają wspólną nazwę. Na przykład tutaj jest lista funkcji o nazwach foo :
A::foo()
B::foo()
C::foo(int)
C::foo(std::string)
Aby je wszystkie rozróżnić, kompilator C ++ utworzy unikalne nazwy dla każdego z nich w procesie zwanym manipulowaniem nazwami lub dekorowaniem. Kompilatory C tego nie robią. Co więcej, każdy kompilator C ++ może to zrobić w inny sposób.
extern „C” mówi kompilatorowi C ++, aby nie wykonywał żadnych zmian nazw w kodzie wewnątrz nawiasów. Pozwala to na wywoływanie funkcji C z poziomu C ++.
Ma to związek ze sposobem, w jaki różne kompilatory wykonują manipulację nazwami. Kompilator C ++ zmienia nazwę symbolu wyeksportowanego z pliku nagłówkowego w zupełnie inny sposób niż kompilator C, więc podczas próby połączenia wystąpiłby błąd konsolidatora informujący, że brakuje symboli.
Aby rozwiązać ten problem, mówimy kompilatorowi C ++, aby działał w trybie „C”, więc dokonuje manipulacji nazw w taki sam sposób, jak kompilator C. Po wykonaniu tej czynności błędy konsolidatora zostały naprawione.
C i C ++ mają różne zasady dotyczące nazw symboli. Symbole to sposób, w jaki konsolidator wie, że wywołanie funkcji „openBankAccount” w jednym pliku obiektowym utworzonym przez kompilator jest odniesieniem do funkcji, którą nazwałeś „openBankAccount” w innym pliku obiektowym utworzonym z innego pliku źródłowego przez ten sam (lub zgodny) kompilator. Pozwala to na stworzenie programu z więcej niż jednego pliku źródłowego, co jest ulgą podczas pracy nad dużym projektem.
W C zasada jest bardzo prosta, symbole i tak znajdują się w jednej przestrzeni nazw. Zatem liczba całkowita „socks” jest przechowywana jako „socks”, a funkcja count_socks jest przechowywana jako „count_socks”.
Linkery zostały zbudowane dla C i innych języków, takich jak C, z tą prostą regułą nazewnictwa symboli. Zatem symbole w konsolidatorze to po prostu proste ciągi.
Ale w C ++ język pozwala mieć przestrzenie nazw, polimorfizm i różne inne rzeczy, które kolidują z tak prostą regułą. Wszystkie sześć twoich funkcji polimorficznych zwanych „add” musi mieć różne symbole, w przeciwnym razie niewłaściwy z nich zostanie użyty przez inne pliki obiektowe. Odbywa się to poprzez „zniekształcanie” (to termin techniczny) nazw symboli.
Kiedy łączysz kod C ++ z bibliotekami lub kodem C, potrzebujesz zewnętrznego „C” wszystkiego, co jest napisane w C, na przykład pliki nagłówkowe dla bibliotek C, aby poinformować kompilator C ++, że te nazwy symboli nie powinny być zniekształcane, podczas gdy reszta Twój kod C ++ oczywiście musi zostać zniekształcony, inaczej nie zadziała.
Kiedy łączysz biblioteki C z plikami obiektowymi C ++
Co się dzieje na poziomie kompilatora / konsolidatora, co wymaga od nas użycia tego?
C i C ++ używają różnych schematów nazywania symboli. Mówi to linkerowi, aby używał schematu C podczas łączenia w danej bibliotece.
W jaki sposób kompilacja / linkowanie rozwiązuje problemy, które wymagają od nas korzystania?
Użycie schematu nazewnictwa C umożliwia odwoływanie się do symboli w stylu C. W przeciwnym razie konsolidator wypróbowałby symbole w stylu C ++, które nie działałyby.
Powinieneś używać extern "C" za każdym razem, gdy dołączasz nagłówek definiujący funkcje rezydujące w pliku skompilowanym przez kompilator C, używanym w pliku C ++. (Wiele standardowych bibliotek C może zawierać to sprawdzenie w swoich nagłówkach, aby ułatwić programistę)
Na przykład, jeśli masz projekt z 3 plikami, util.c, util.h i main.cpp, a oba pliki .c i .cpp są kompilowane za pomocą kompilatora C ++ (g ++, cc itd.), To nie jest naprawdę potrzebne, a może nawet powodować błędy konsolidatora. Jeśli twój proces budowania używa zwykłego kompilatora C dla util.c, to będziesz musiał użyć extern "C", dołączając plik util.h.
Dzieje się tak, że C ++ koduje parametry funkcji w jej nazwie. Tak działa przeciążanie funkcji. Wszystko, co zwykle dzieje się z funkcją C, to dodanie podkreślenia („_”) na początku nazwy. Bez użycia extern "C" linker będzie szukał funkcji o nazwie DoSomething @@ int @ float (), gdy rzeczywista nazwa funkcji to _DoSomething () lub po prostu DoSomething ().
Użycie extern „C” rozwiązuje powyższy problem, mówiąc kompilatorowi C ++, że powinien szukać funkcji zgodnej z konwencją nazewnictwa języka C zamiast konwencji C ++.
Kompilator C ++ tworzy nazwy symboli inaczej niż kompilator C. Tak więc, jeśli próbujesz wywołać funkcję, która znajduje się w pliku C, skompilowanej jako kod w C, musisz powiedzieć kompilatorowi C ++, że nazwy symboli, które próbuje rozwiązać, wyglądają inaczej niż domyślnie; w przeciwnym razie krok łącza nie powiedzie się.
extern "C" {}Konstrukt instruuje kompilator nie wykonywać maglowania na nazw zadeklarowanych w nawiasach. Zwykle kompilator C ++ „ulepsza” nazwy funkcji, tak aby kodowały informacje o typach argumentów i zwracaną wartość; nazywa się to zniekształconą nazwą . extern "C"Konstrukcja zapobiega przekręcona.
Jest zwykle używany, gdy kod C ++ musi wywołać bibliotekę języka C. Może być również używany podczas udostępniania funkcji C ++ (na przykład z biblioteki DLL) klientom C.
Służy do rozwiązywania problemów związanych z zniekształcaniem nazw. extern C oznacza, że funkcje znajdują się w „płaskim” interfejsie API w stylu C.
Num:ValueSizeTypeBindVisNdxName8:00000000000000006 FUNC GLOBAL DEFAULT 1 _Z1fv
9:00000000000000066 FUNC GLOBAL DEFAULT 1 ef
10:000000000000000c16 FUNC GLOBAL DEFAULT 1 _Z1hv
11:00000000000000000 NOTYPE GLOBAL DEFAULT UND _Z1gv
12:00000000000000000 NOTYPE GLOBAL DEFAULT UND eg
Interpretacja
Widzimy to:
efi egzostały zapisane w symbolach o takiej samej nazwie jak w kodzie
inne symbole zostały zniekształcone. Rozplączmy je:
Wniosek: oba poniższe typy symboli nie zostały zniekształcone:
zdefiniowane
zadeklarowane, ale niezdefiniowane ( Ndx = UND), do podania w dowiązaniu lub w czasie wykonywania z innego pliku obiektowego
Więc do extern "C"dzwonienia będziesz potrzebować obu:
C z C ++: g++nakazuje oczekiwanie niezarządzonych symboli utworzonych przezgcc
C ++ z C: g++nakazuje generowanie niezarządzanych symboli gccdo użycia
Rzeczy, które nie działają w zewnętrznym C.
Staje się oczywiste, że żadna funkcja C ++, która wymaga zmiany nazw, 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++. */#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);}
#ifdef __cplusplus extern "C" { #endif
Więc gdy są dołączone z pliku C ++, nadal są traktowane jako nagłówek C.extern "C" określa, jak należy nazywać symbole w generowanym pliku obiektowym. Jeśli funkcja jest zadeklarowana bez zewnętrznego „C”, nazwa symbolu w pliku obiektowym będzie używać zamiany nazw C ++. Oto przykład.
Podany test C tak:
Kompilowanie i wyświetlanie symboli w pliku obiektowym daje:
W rzeczywistości funkcja foo nazywa się „_Z3foov”. Ten ciąg zawiera między innymi informacje o typie zwracanego typu i parametrach. Jeśli zamiast tego napiszesz test.C w ten sposób:
Następnie skompiluj i spójrz na symbole:
Otrzymujesz połączenie C. Nazwa funkcji „foo” w pliku obiektowym to po prostu „foo” i nie zawiera ona wszystkich wyszukanych informacji o typie, które pochodzą z zamiany nazw.
Zwykle umieszczasz nagłówek w extern „C” {}, jeśli kod, który jest z nim związany, został skompilowany za pomocą kompilatora C, ale próbujesz wywołać go z C ++. Kiedy to zrobisz, mówisz kompilatorowi, że wszystkie deklaracje w nagłówku będą używać połączenia C. Kiedy połączysz swój kod, twoje pliki .o będą zawierały odniesienia do „foo”, a nie „_Z3fooblah”, co, miejmy nadzieję, pasuje do wszystkiego, co jest w bibliotece, do której tworzysz łącze.
Większość współczesnych bibliotek umieszcza osłony wokół takich nagłówków, aby symbole były deklarowane z odpowiednim połączeniem. np. w wielu standardowych nagłówkach znajdziesz:
Daje to pewność, że gdy kod C ++ zawiera nagłówek, symbole w pliku obiektowym są zgodne z tym, co jest w bibliotece C. Powinieneś tylko umieścić extern "C" {} wokół swojego nagłówka C, jeśli jest stary i nie ma już tych zabezpieczeń.
źródło
W C ++ możesz mieć różne encje, które mają wspólną nazwę. Na przykład tutaj jest lista funkcji o nazwach foo :
A::foo()
B::foo()
C::foo(int)
C::foo(std::string)
Aby je wszystkie rozróżnić, kompilator C ++ utworzy unikalne nazwy dla każdego z nich w procesie zwanym manipulowaniem nazwami lub dekorowaniem. Kompilatory C tego nie robią. Co więcej, każdy kompilator C ++ może to zrobić w inny sposób.
extern „C” mówi kompilatorowi C ++, aby nie wykonywał żadnych zmian nazw w kodzie wewnątrz nawiasów. Pozwala to na wywoływanie funkcji C z poziomu C ++.
źródło
Ma to związek ze sposobem, w jaki różne kompilatory wykonują manipulację nazwami. Kompilator C ++ zmienia nazwę symbolu wyeksportowanego z pliku nagłówkowego w zupełnie inny sposób niż kompilator C, więc podczas próby połączenia wystąpiłby błąd konsolidatora informujący, że brakuje symboli.
Aby rozwiązać ten problem, mówimy kompilatorowi C ++, aby działał w trybie „C”, więc dokonuje manipulacji nazw w taki sam sposób, jak kompilator C. Po wykonaniu tej czynności błędy konsolidatora zostały naprawione.
źródło
C i C ++ mają różne zasady dotyczące nazw symboli. Symbole to sposób, w jaki konsolidator wie, że wywołanie funkcji „openBankAccount” w jednym pliku obiektowym utworzonym przez kompilator jest odniesieniem do funkcji, którą nazwałeś „openBankAccount” w innym pliku obiektowym utworzonym z innego pliku źródłowego przez ten sam (lub zgodny) kompilator. Pozwala to na stworzenie programu z więcej niż jednego pliku źródłowego, co jest ulgą podczas pracy nad dużym projektem.
W C zasada jest bardzo prosta, symbole i tak znajdują się w jednej przestrzeni nazw. Zatem liczba całkowita „socks” jest przechowywana jako „socks”, a funkcja count_socks jest przechowywana jako „count_socks”.
Linkery zostały zbudowane dla C i innych języków, takich jak C, z tą prostą regułą nazewnictwa symboli. Zatem symbole w konsolidatorze to po prostu proste ciągi.
Ale w C ++ język pozwala mieć przestrzenie nazw, polimorfizm i różne inne rzeczy, które kolidują z tak prostą regułą. Wszystkie sześć twoich funkcji polimorficznych zwanych „add” musi mieć różne symbole, w przeciwnym razie niewłaściwy z nich zostanie użyty przez inne pliki obiektowe. Odbywa się to poprzez „zniekształcanie” (to termin techniczny) nazw symboli.
Kiedy łączysz kod C ++ z bibliotekami lub kodem C, potrzebujesz zewnętrznego „C” wszystkiego, co jest napisane w C, na przykład pliki nagłówkowe dla bibliotek C, aby poinformować kompilator C ++, że te nazwy symboli nie powinny być zniekształcane, podczas gdy reszta Twój kod C ++ oczywiście musi zostać zniekształcony, inaczej nie zadziała.
źródło
Kiedy łączysz biblioteki C z plikami obiektowymi C ++
C i C ++ używają różnych schematów nazywania symboli. Mówi to linkerowi, aby używał schematu C podczas łączenia w danej bibliotece.
Użycie schematu nazewnictwa C umożliwia odwoływanie się do symboli w stylu C. W przeciwnym razie konsolidator wypróbowałby symbole w stylu C ++, które nie działałyby.
źródło
Powinieneś używać extern "C" za każdym razem, gdy dołączasz nagłówek definiujący funkcje rezydujące w pliku skompilowanym przez kompilator C, używanym w pliku C ++. (Wiele standardowych bibliotek C może zawierać to sprawdzenie w swoich nagłówkach, aby ułatwić programistę)
Na przykład, jeśli masz projekt z 3 plikami, util.c, util.h i main.cpp, a oba pliki .c i .cpp są kompilowane za pomocą kompilatora C ++ (g ++, cc itd.), To nie jest naprawdę potrzebne, a może nawet powodować błędy konsolidatora. Jeśli twój proces budowania używa zwykłego kompilatora C dla util.c, to będziesz musiał użyć extern "C", dołączając plik util.h.
Dzieje się tak, że C ++ koduje parametry funkcji w jej nazwie. Tak działa przeciążanie funkcji. Wszystko, co zwykle dzieje się z funkcją C, to dodanie podkreślenia („_”) na początku nazwy. Bez użycia extern "C" linker będzie szukał funkcji o nazwie DoSomething @@ int @ float (), gdy rzeczywista nazwa funkcji to _DoSomething () lub po prostu DoSomething ().
Użycie extern „C” rozwiązuje powyższy problem, mówiąc kompilatorowi C ++, że powinien szukać funkcji zgodnej z konwencją nazewnictwa języka C zamiast konwencji C ++.
źródło
Kompilator C ++ tworzy nazwy symboli inaczej niż kompilator C. Tak więc, jeśli próbujesz wywołać funkcję, która znajduje się w pliku C, skompilowanej jako kod w C, musisz powiedzieć kompilatorowi C ++, że nazwy symboli, które próbuje rozwiązać, wyglądają inaczej niż domyślnie; w przeciwnym razie krok łącza nie powiedzie się.
źródło
extern "C" {}
Konstrukt instruuje kompilator nie wykonywać maglowania na nazw zadeklarowanych w nawiasach. Zwykle kompilator C ++ „ulepsza” nazwy funkcji, tak aby kodowały informacje o typach argumentów i zwracaną wartość; nazywa się to zniekształconą nazwą .extern "C"
Konstrukcja zapobiega przekręcona.Jest zwykle używany, gdy kod C ++ musi wywołać bibliotekę języka C. Może być również używany podczas udostępniania funkcji C ++ (na przykład z biblioteki DLL) klientom C.
źródło
Służy do rozwiązywania problemów związanych z zniekształcaniem nazw. extern C oznacza, że funkcje znajdują się w „płaskim” interfejsie API w stylu C.
źródło
Zdekompiluj
g++
wygenerowany plik binarny, aby zobaczyć, co się dziejeAby zrozumieć, dlaczego
extern
jest to konieczne, najlepiej jest zrozumieć szczegółowo, co dzieje się w plikach obiektowych, na przykładzie:main.cpp
Skompiluj z wyjściem ELF GCC 4.8 Linux :
Zdekompiluj tablicę symboli:
Wynik zawiera:
Interpretacja
Widzimy to:
ef
ieg
zostały zapisane w symbolach o takiej samej nazwie jak w kodzieinne symbole zostały zniekształcone. Rozplączmy je:
Wniosek: oba poniższe typy symboli nie zostały zniekształcone:
Ndx = UND
), do podania w dowiązaniu lub w czasie wykonywania z innego pliku obiektowegoWięc do
extern "C"
dzwonienia będziesz potrzebować obu:g++
nakazuje oczekiwanie niezarządzonych symboli utworzonych przezgcc
g++
nakazuje generowanie niezarządzanych symboligcc
do użyciaRzeczy, które nie działają w zewnętrznym C.
Staje się oczywiste, że żadna funkcja C ++, która wymaga zmiany nazw, nie będzie działać w środku
extern C
:Minimalne uruchamialne C z przykładu C ++
Ze względu na kompletność i dla nowicjuszy zobacz także: Jak używać plików źródłowych C w projekcie C ++?
Wywołanie C z C ++ jest całkiem proste: każda funkcja C ma tylko jeden możliwy niezmieniony symbol, więc nie jest wymagana żadna dodatkowa praca.
main.cpp
ch
cc
Biegać:
Bez
extern "C"
linku nie powiedzie się z:ponieważ
g++
spodziewa się znaleźć zniekształconąf
, któragcc
nie wyprodukowała.Przykład na GitHub .
Minimalny runnable C ++ z przykładu C.
Wywołanie C ++ z jest nieco trudniejsze: musimy ręcznie stworzyć niezmienione wersje każdej funkcji, którą chcemy ujawnić.
Tutaj ilustrujemy, jak uwidocznić przeciążenia funkcji C ++ w C.
main.c
cpp.h
cpp.cpp
Biegać:
Bez
extern "C"
niego zawodzi z:ponieważ
g++
wygenerowano zniekształcone symbole, którychgcc
nie można znaleźć.Przykład na GitHub .
Testowane w Ubuntu 18.04.
źródło