Co dokładnie robi wstawianie extern "C"
do kodu C ++?
Na przykład:
extern "C" {
void foo();
}
foo()
funkcję.
Co dokładnie robi wstawianie extern "C"
do kodu C ++?
Na przykład:
extern "C" {
void foo();
}
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:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
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" { 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; }
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.
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 w extern "C"
bloku.
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(); }
Skompiluj i zdemontuj wygenerowane wyjście ELF :
g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
Dane wyjściowe zawierają:
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
Interpretacja
Widzimy to:
ef
i eg
były przechowywane w symbolach o takiej samej nazwie jak w kodzie
pozostałe symbole były zniekształcone. Odznaczmy je:
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
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 symbole gcc
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
:
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 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
#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; }
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ę:
main.cpp:6: undefined reference to `f()'
ponieważ g++
oczekuje znalezienia zniekształconego f
, którego gcc
nie wyprodukował.
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
#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"
tego zawiedzie:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
ponieważ g++
wygenerowane zniekształcone symbole, których gcc
nie można znaleźć.
Testowane w Ubuntu 18.04.
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()
i dlopen()
do wywoływania takich funkcji.
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 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;
}
Kompilator prądu zmiennego nie skompiluje powyższego przykładu, ponieważ ta sama funkcja printMe
jest zdefiniowana dwukrotnie (mimo że mają inne parametry int a
niż 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 printMe
jest 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 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);
extern "C"
jeśli mamy tylko dll
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 ++:
extern "C" {
struct method {
int virtual;
};
}
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.
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 .
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.
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>
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"
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 to
Typ 1:
extern "language" function-prototype
Typ 2:
extern "language"
{
function-prototype
};
na przykład:
#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)
{
//
//
}
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.
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 ++.
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).
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 ++.
extern "C"
funkcję - i (z zastrzeżeniem pewnych ograniczeń) będzie można ją wywoływać za pomocą kodu skompilowanego przez kompilator C.