Odpowiedzi:
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ć, extern
ponieważ większość nagłówków systemowych C będzie już uwzględniać fakt, że mogą być zawarte w kodzie C ++ i już w extern
swoim kodzie.
#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:
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:
#ifdef __cplusplus
extern "C" {
#endif
... declarations ...
#ifdef __cplusplus
}
#endif
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 powinniśmy go używać?
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.
Zdekompiluj g++
wygenerowany plik binarny, aby zobaczyć, co się dzieje
Aby zrozumieć, dlaczego extern
jest to konieczne, najlepiej jest zrozumieć szczegółowo, co dzieje się w plikach obiektowych, na przykładzie:
main.cpp
void f() {}
void g();
extern "C" {
void ef() {}
void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
Skompiluj z wyjściem ELF GCC 4.8 Linux :
g++ -c main.cpp
Zdekompiluj tablicę symboli:
readelf -s main.o
Wynik zawiera:
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
Interpretacja
Widzimy to:
ef
i eg
zostały zapisane w symbolach o takiej samej nazwie jak w kodzie
inne symbole zostały zniekształcone. Rozplączmy je:
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
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 symboli gcc
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
:
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) { }
}
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
#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++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif
#endif
cc
#include "c.h"
int f(void) { return 1; }
Biegać:
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
Bez extern "C"
linku nie powiedzie się z:
main.cpp:6: undefined reference to `f()'
ponieważ g++
spodziewa się znaleźć zniekształconą f
, która gcc
nie wyprodukowała.
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
#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);
}
Biegać:
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
Bez extern "C"
niego zawodzi z:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
ponieważ g++
wygenerowano zniekształcone symbole, których gcc
nie można znaleźć.
Testowane w Ubuntu 18.04.