Wat is het effect van externe “C” in C++?

Wat doet het precies om extern "C"in C++-code te plaatsen?

Bijvoorbeeld:

extern "C" {
   void foo();
}

Antwoord 1, autoriteit 100%

extern "C"zorgt ervoor dat een functienaam in C++ een C-koppeling heeft (compiler vervormt de naam niet) zodat client C-code kan linken naar (gebruiken) uw functie met behulp van een C-compatibele header bestand dat alleen de declaratie van uw functie bevat. Uw functiedefinitie is opgenomen in een binair formaat (dat is gecompileerd door uw C++-compiler) waarnaar de client C-linker vervolgens zal linken met behulp van de C-naam.

Omdat C++ een overbelasting van functienamen heeft en C niet, kan de C++-compiler niet zomaar de functienaam gebruiken als een unieke id om naar te linken, dus verminkt het de naam door informatie over de argumenten toe te voegen. AC-compiler hoeft de naam niet te mangelen, omdat u functienamen in C niet kunt overbelasten. Wanneer u stelt dat een functie een extern "C"-koppeling heeft in C++, voegt de C++-compiler geen argument/parameter toe typ informatie in de naam die wordt gebruikt voor koppeling.

Voor de duidelijkheid, je kunt een extern "C"koppeling aan elke individuele verklaring/definitie expliciet specificeren of een blok gebruiken om een reeks verklaringen/definities te groeperen om een bepaalde koppeling te krijgen:

p>

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

Als je geïnteresseerd bent in de technische details, ze staan vermeld in sectie 7.5 van de C++03-standaard, hier is een korte samenvatting (met de nadruk op extern "C"):

  • extern "C"is een koppelingsspecificatie
  • Elke compiler is vereistom “C”-koppeling te bieden
  • Een koppelingsspecificatie komt alleen voor in het bereik van de naamruimte
  • Alle functietypes, functienamen en variabelenamen hebben een taalkoppeling Zie de opmerking van Richard:Alleen functienamen en variabelenamen met externe koppeling hebben een taalkoppeling
  • Twee functietypen met verschillende taalkoppelingen zijn verschillende typen, zelfs als ze verder identiek zijn
  • Koppelingsspecificaties nest, binnenste bepaalt de uiteindelijke koppeling
  • extern "C"wordt genegeerd voor klasleden
  • Maximaal één functie met een bepaalde naam kan een “C”-koppeling hebben (ongeacht de naamruimte)
  • extern "C"dwingt een functie om externe koppeling te hebben (kan het niet statisch maken) Zie de opmerking van Richard:staticbinnenkant extern "C"is geldig; een aldus gedeclareerde entiteit heeft een interne koppeling en heeft dus geen taalkoppeling
  • Koppeling van C++ naar objecten gedefinieerd in andere talen en naar objecten gedefinieerd in C++ vanuit andere talen is implementatie-gedefinieerd en taalafhankelijk. Alleen wanneer de objectlay-outstrategieën van twee taalimplementaties voldoende vergelijkbaar zijn, kan een dergelijke koppeling worden bereikt

Antwoord 2, autoriteit 22%

Ik wilde alleen wat informatie toevoegen, aangezien ik het nog niet gepost heb gezien.

Je ziet heel vaak code in C-headers, zoals:

#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif

Wat dit bereikt, is dat je dat C-headerbestand kunt gebruiken met je C++-code, omdat de macro “__cplusplus” wordt gedefinieerd. Maar je kunt het ooknog steeds gebruiken met je oude C-code, waarbij de macro NIETis gedefinieerd, zodat het de unieke C++-constructie niet ziet.

Hoewel ik ook C++-code heb gezien, zoals:

extern "C" {
#include "legacy_C_header.h"
}

waarvan ik me voorstel dat het ongeveer hetzelfde bereikt.

Ik weet niet zeker welke manier beter is, maar ik heb ze allebei gezien.


Antwoord 3, autoriteit 20%

Decompileer een door g++gegenereerd binair bestand om te zien wat er aan de hand is

main.cpp

void f() {}
void g();
extern "C" {
    void ef() {}
    void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

Compileer en demonteer de gegenereerde ELFuitvoer:

g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o

De uitvoer bevat:

    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

Interpretatie

We zien dat:

  • efen egwerden opgeslagen in symbolen met dezelfde naam als in de code

  • de andere symbolen waren verminkt. Laten we ze ontwarren:

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()
    

Conclusie: beide volgende symbooltypes zijn nietverminkt:

  • gedefinieerd
  • verklaard maar niet gedefinieerd (Ndx = UND), te verstrekken via link of runtime vanuit een ander objectbestand

Je hebt dus extern "C"nodig als je belt:

  • C van C++: vertel g++om onvervormde symbolen te verwachten die zijn geproduceerd door gcc
  • C++ van C: vertel g++om onvervormde symbolen te genereren die gcckan gebruiken

Dingen die niet werken in externe C

Het wordt duidelijk dat elke C++-functie die het mangelen van namen vereist, niet werkt binnen 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) { }
}

Minimaal uitvoerbare C uit C++ voorbeeld

Voor de volledigheid en voor de nieuwkomers, zie ook: Hoe C-bronbestanden gebruiken in een C++-project?

C aanroepen vanuit C++ is vrij eenvoudig: elke C-functie heeft slechts één mogelijk niet-verminkt symbool, dus er is geen extra werk vereist.

main.cpp

#include <cassert>
#include "c.h"
int main() {
    assert(f() == 1);
}

c.h

#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

c.c

#include "c.h"
int f(void) { return 1; }

Uitvoeren:

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

Zonder extern "C"mislukt de link met:

main.cpp:6: undefined reference to `f()'

omdat g++verwacht een verminkte fte vinden, die gccniet heeft geproduceerd.

Voorbeeld op GitHub.

Minimaal uitvoerbare C++ van C-voorbeeld

C++ aanroepen vanuit C is een beetje moeilijker: we moeten handmatig niet-verminkte versies maken van elke functie die we willen blootleggen.

Hier laten we zien hoe je overbelasting van C++-functies blootstelt aan 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);
}

Uitvoeren:

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

Zonder extern "C"mislukt het met:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

omdat g++verminkte symbolen genereerde die gccniet kan vinden.

Voorbeeld op GitHub.

Waar is de extern "C"als ik C-headers van C++ opneem?

Getest in Ubuntu 18.04.


Antwoord 4, autoriteit 12%

In elk C++-programma worden alle niet-statische functies als symbolen in het binaire bestand weergegeven. Deze symbolen zijn speciale tekenreeksen die een functie in het programma op unieke wijze identificeren.

In C is de symboolnaam hetzelfde als de functienaam. Dit is mogelijk omdat in C geen twee niet-statische functies dezelfde naam kunnen hebben.

Omdat C++ overbelasting toestaat en veel functies heeft die C niet heeft — zoals klassen, lidfuncties, uitzonderingsspecificaties — is het niet mogelijk om simpelweg de functienaam als symboolnaam te gebruiken. Om dat op te lossen, gebruikt C++ zogenaamde naam-mangling, die de functienaam en alle benodigde informatie (zoals het aantal en de grootte van de argumenten) omzet in een vreemd uitziende string die alleen door de compiler en linker wordt verwerkt.

Dus als je een functie specificeert als extern C, voert de compiler er geen naamverwisseling mee uit en kan het direct
toegankelijk met de symboolnaam als functienaam.

Dit is handig bij het gebruik van dlsym()en dlopen()voor het aanroepen van dergelijke functies.


Antwoord 5, autoriteit 4%

C++ vervormt functienamen om een objectgeoriënteerde taal te creëren vanuit een proceduretaal

De meeste programmeertalen zijn niet bovenop bestaande programmeertalen gebouwd. C++ is bovenop C gebouwd, en bovendien is het een objectgeoriënteerde programmeertaal die is opgebouwd uit een procedurele programmeertaal, en om die reden zijn er C++-expressies zoals extern "C"die achterwaartse compatibiliteit bieden met C.

Laten we naar het volgende voorbeeld kijken:

#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;
}

AC-compiler zal het bovenstaande voorbeeld niet compileren, omdat dezelfde functie printMetwee keer is gedefinieerd (ook al hebben ze verschillende parameters int avs char a).

gcc -o printMe printMe.c && ./printMe;
1 fout. PrintMe is meer dan eens gedefinieerd.

Een C++-compiler zal het bovenstaande voorbeeld compileren. Het maakt niet uit dat printMetwee keer is gedefinieerd.

g++ -o printMe printMe.c && ./printMe;

Dit komt omdat een C++-compiler impliciet de naam (mangelt) functies hernoemt op basis van hun parameters. In C werd deze functie niet ondersteund. Toen C++ echter over C werd gebouwd, was de taal ontworpen om objectgeoriënteerd te zijn en moest deze de mogelijkheid ondersteunen om verschillende klassen met methoden (functies) met dezelfde naam te maken en methoden te overschrijven (methode overschrijven) op basis van verschillende parameters.

extern "C"zegt “verval de C-functienamen niet”

Stel je echter voor dat we een oud C-bestand hebben met de naam “parent.c” dat de functienamen van includebevat van andere verouderde C-bestanden, “parent.h”, “child.h”, enz. Als het oude “parent.c”-bestand door een C++-compiler wordt geleid, worden de functienamen verminkt en komen ze niet langer overeen met de functienamen die zijn opgegeven in “parent.h”, “child.h”, enz. – dus de functienamen in die externe bestanden zouden ook moeten worden verminkt. Het mangelen van functienamen in een complex C-programma, programma’s met veel afhankelijkheden, kan leiden tot gebroken code; dus het kan handig zijn om een sleutelwoord op te geven dat de C++-compiler kan vertellen een functienaam niet te manipuleren.

Het extern "C"sleutelwoord vertelt een C++ compiler om C-functienamen niet te mangelen (hernoemen).

Bijvoorbeeld:

extern "C" void printMe(int a);


Antwoord 6, autoriteit 2%

Geen enkele C-header kan compatibel worden gemaakt met C++ door alleen externe “C” in te pakken. Wanneer identifiers in een C-header conflicteren met C++ trefwoorden zal de C++ compiler hierover klagen.

Ik heb bijvoorbeeld de volgende code zien mislukken in een g++ :

extern "C" {
struct method {
    int virtual;
};
}

Een beetje logisch, maar het is iets om in gedachten te houden bij het overzetten van C-code naar C++.


Antwoord 7, autoriteit 2%

Het verandert de koppeling van een functie zodanig dat de functie kan worden aangeroepen vanuit C. In de praktijk betekent dit dat de functienaam niet verminkt.


Antwoord 8

Het informeert de C++-compiler om de namen van die functies in een C-stijl op te zoeken bij het koppelen, omdat de namen van functies die in C en C++ zijn gecompileerd tijdens de koppelingsfase anders zijn.


Antwoord 9

extern "C"is bedoeld om te worden herkend door een C++-compiler en om de compiler te informeren dat de genoteerde functie is (of zal worden) gecompileerd in C-stijl, zodat deze tijdens het koppelen linkt naar de juiste versie van de functie van C.


Antwoord 10

extern "C"is een koppelingsspecificatie die wordt gebruikt om C-functies aan te roepenin de Cpp-bronbestanden. We kunnen C-functies aanroepen, variabelen schrijven, & kopteksten opnemen. Functie wordt gedeclareerd in externe entiteit & het is buiten gedefinieerd. Syntaxis is

Type 1:

extern "language" function-prototype

Type 2:

extern "language"
{
     function-prototype
};

bijvoorbeeld:

#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)
{
    //
    //
}

Antwoord 11

Ik heb eerder ‘extern “C”‘ gebruikt voor dll-bestanden (dynamic link library) om de functie main() enz. “exporteerbaar” te maken, zodat het later in een ander uitvoerbaar bestand van dll kan worden gebruikt.
Misschien kan een voorbeeld van waar ik het gebruikte nuttig zijn.

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);
}

Antwoord 12

Dit antwoord is voor de ongeduldige/deadlines die ze moeten halen, slechts een deel/eenvoudige uitleg staat hieronder:

  • in C++ kun je dezelfde naam in de klas hebben via overbelasting (omdat ze bijvoorbeeld allemaal dezelfde naam hebben, kunnen ze niet worden geëxporteerd zoals ze zijn vanuit dll, enz.) Oplossing voor deze problemen is dat ze worden geconverteerd naar verschillende tekenreeksen (symbolen genoemd), symbolen houden rekening met de naam van de functie, ook de argumenten, zodat elk van deze functies, zelfs met dezelfde naam, uniek kan worden geïdentificeerd (ook wel naamverwisseling genoemd)
  • in C heb je geen overbelasting, de functienaam is uniek (dus een aparte string om de functienaam uniek te identificeren is niet vereist, dus symbool is de functienaam zelf)

Dus
in C++, met naam die unieke identiteiten vervormt voor elke functie
in C, zelfs zonder de unieke identiteiten van elke functie te mangelen

Om het gedrag van C++ te veranderen, dat wil zeggen, om te specificeren dat naamverwisseling nietmag gebeuren voor een bepaalde functie, kun je externe “C”gebruiken voor de functie naam, om welke reden dan ook, zoals het exporteren van een functie met een specifieke naam uit een dll, voor gebruik door zijn klanten.

Lees andere antwoorden voor meer gedetailleerde/juiste antwoorden.


Antwoord 13

Een functie void f() gecompileerd door een C-compiler en een functie met dezelfde naam void f() gecompileerd door een C++-compiler zijn niet dezelfde functie. Als je die functie in C hebt geschreven en je probeert hem vanuit C++ aan te roepen, dan zou de linker naar de C++-functie zoeken en de C-functie niet vinden.

extern “C” vertelt de C++-compiler dat je een functie hebt die is gecompileerd door de C-compiler. Zodra je het vertelt dat het is gecompileerd door de C-compiler, weet de C++-compiler hoe het correct moet worden aangeroepen.

Het stelt de C++-compiler ook in staat om een C++-functie op zo’n manier te compileren dat de C-compiler deze kan aanroepen. Die functie zou officieel een C-functie zijn, maar aangezien hij is gecompileerd door de C++-compiler, kan hij alle C++-functies gebruiken en heeft hij alle C++-sleutelwoorden.


Antwoord 14

Bij het mixen van C en C++ (d.w.z. a. het aanroepen van de C-functie vanuit C++; en b. het aanroepen van de C++-functie vanuit C), veroorzaakt het mangelen van de C++-naam koppelingsproblemen. Technisch gesproken doet dit probleem zich alleen voor als de aangeroepen functies al binair zijn gecompileerd (hoogstwaarschijnlijk een *.a-bibliotheekbestand) met behulp van de bijbehorende compiler.

We moeten dus extern “C” gebruiken om het mangelen van namen in C++ uit te schakelen.


Antwoord 15

Zonder in strijd te zijn met andere goede antwoorden, zal ik een beetje van mijn voorbeeld toevoegen.

Wat C++ Compilerprecies doet: het verminkt de namen in het compilatieproces, daarom moeten we de compiler vertellen om de behandelenCimplementatie speciaal .

Als we C++-klassen maken en extern "C"toevoegen, vertellen we onze C++-compiler dat we de C-aanroepconventie gebruiken.

Reden (we noemen C-implementatie vanuit C++):ofwel willen we de C-functie aanroepen vanuit C++ of de C++-functie aanroepen vanuit C (C++-klassen … enz. werken niet in C).


Antwoord 16

Raadpleeg de onderstaande link, een uitleg voor geeks voor geeks voor het gebruik van externe “C”
Belangrijke informatie toevoegen vanaf de onderstaande pagina

Overweeg de volgende declaraties van functie f()

int  f (void) { return 1; }
int  f (int)  { return 0; }
void g (void) { int i = f(), j = f(0); }

Een C++-compiler kan bovenstaande namen manipuleren tot het volgende (Bron: Wiki)

int  __f_v (void) { return 1; }
int  __f_i (int)  { return 0; }
void __g_v (void) { int i = __f_v(), j = __f_i(0); }

https://www.geeksforgeeks.org/extern-c-in- c/

Other episodes