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:
efi egbył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 przezgccg++aby wygenerować nie zniekształcone symbole gccdo 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 gccnie 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 gccnie 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 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.
Na przykład:
extern "C" void printMe(int a);
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).
Ż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, 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.
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.