Jaki jest efekt zewnętrznego „C” w C ++?

1629

Co dokładnie robi wstawianie extern "C"do kodu C ++?

Na przykład:

extern "C" {
   void foo();
}
Litherum
źródło
82
Chciałbym przedstawić wam ten artykuł: http://www.agner.org/optimize/calling_conventions.pdf Mówi o wiele więcej na temat konwencji wywoływania i różnicy między kompilatorami.
Sam Liao,
1
@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:

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” 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
Faisal Vali
źródło
21
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.

UncaAlby
źródło
11
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(); }

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:

  • 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 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ł.

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

#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źć.

Przykład na GitHub .

Testowane w Ubuntu 18.04.

Ciro Santilli
źródło
21
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.

sud03r
źródło
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 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);

tfmontague
źródło
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 {
    int virtual;
};
}

Trochę ma to sens, ale należy o tym pamiętać przy przenoszeniu kodu C do C ++.

Sander Mertens
źródło
14
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 .

Zatrudniony rosyjski
źródło
3
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.

Mark Rushakoff
źródło
12

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.

Flami
źródło
6

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);
}
SturmCoder
źródło
4
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>
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)
{
    //
    //
}
Yogeesh HT
źródło
3

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.

Manohar Reddy Poreddy
źródło
1

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 ++.

Trombe
źródło
0

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).

Susobhan Das
źródło
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 ++.

gnasher729
źródło
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.
Jonathan Leffler