Einfach ausgedrückt ist externes „C“ eine Möglichkeit für C++, C-Sprachsymbole für die Kompatibilität mit C zu deklarieren oder zu definieren. Das ist leicht zu sagen, aber es erfordert dennoch einige Mühe, es zu verstehen. Zunächst müssen wir mit dem Unterschied zwischen C++ und C beginnen.
Symbol
Wie wir alle wissen, müssen wir vom Code bis zum ausführbaren Programm zwei Prozesse durchlaufen: Kompilierung und Verknüpfung. In der Kompilierungsphase werden die Syntaxerkennung und die Codeerweiterung durchgeführt. Darüber hinaus werden Variablen in Symbole umgewandelt. Beim Verknüpfen werden sie tatsächlich durch Symbole lokalisiert. Wenn der Compiler C- und C++-Code kompiliert, ist der Prozess der Konvertierung von Variablen in Symbole unterschiedlich. Der in diesem Artikel verwendete Compiler ist gcc4.4.7
Sehen wir uns zunächst einen einfachen Code an
/* hello.c */ #include <stdio.h> const char* g_prefix = "hello "; void hello(const char* name) { printf("%s%s", g_prefix, name); }
Beachten Sie, dass der Dateiname hier hello.c ist. Wir führen die Kompilierung gcc -c aus hello.c ruft die Zieldatei hello.o ab. Verwenden Sie nm, um die Symboltabelle der Zieldatei unter Linux zu überprüfen und die folgenden Ergebnisse zu erhalten (das $-Symbol stellt die Shell-Eingabeaufforderung dar)
$ nm hello.o 0000000000000000 D g_prefix 0000000000000000 T hello U printf
Das ist Das Symbol nach der Kompilierung des C-Codes, dessen dritte Spalte der kompilierte Symbolname ist, wird hauptsächlich durch die von uns definierte globale Variable g_prefix betrachtet Code. Wir benennen hello.c in hello.cpp um, kompilieren gcc -c hello.cpp neu, um hello.o zu erhalten, und zeigen es mit nm an. Die Ergebnisse sind wie folgt
0000000000000000 T _Z5helloPKc U __gxx_personality_v0 0000000000000000 D g_prefix U printf
Dies ist die Symbolliste nach dem C++-Code wird kompiliert, gcc identifiziert C- und C++-Codes automatisch anhand des Dateisuffixnamens. Zu diesem Zeitpunkt stellen wir fest, dass sich das Symbol von g_prefix nicht geändert hat, aber das Symbol der Funktion hello hat sich in _Z5helloPKc geändert Die Verarbeitungsmethode von gcc beim Kompilieren von C- und C++-Codes ist anders: Bei C-Code ist der symbolische Name der Variablen die Variable selbst (früher fügte der Compiler vor der C-Codevariablen einen Unterstrich _ hinzu, jetzt ist dies nicht mehr der Fall). Sie können dies standardmäßig tun. Sie können die Kompilierungsoption -fno-leading-underscore (während der Kompilierung) und -fleading-underscore (zur expliziten Festlegung) übergeben, und für C++-Code, wenn es sich um eine Datenvariable handelt und keine Verschachtelung vorliegt, den Symbolnamen ist selbst, wenn der Variablenname verschachtelt ist (in einem Namespace oder einer Klasse) oder es sich um einen Funktionsnamen handelt, wird das Symbol Der Name wird gemäß den folgenden Regeln verarbeitet
1. Das Symbol beginnt mit _Z
2. Bei einer Verschachtelung folgt N, dann der Name der Klasse oder Funktion. Die Zahl vor dem Namen ist die Länge und endet mit E
3. Wenn keine Verschachtelung vorliegt, folgt direkt die Länge des Namens
4. Schließlich gibt es die Parameterliste. Die entsprechende Beziehung zwischen Typen und Symbolen ist wie folgt
int -> i float -> f double -> d char -> c void -> v const -> K * -> P
Dadurch ist es leicht zu verstehen, warum das void hello (const char*) im C++-Code nach der Kompilierung das Symbol _Z5helloPKc hat (PKc wird von rechts nach links als char const * in den Typ übersetzt, das ist die interne Darstellung von Für den Compiler ist die Darstellung, die wir gewohnt sind, const char*, die beiden sind gleich), das C++-Filter-Tool kann den Namen aus dem Symbol ableiten, die Verwendungsmethode ist c++filt _Z5helloPKc
Es ist auch leicht zu verstehen, warum C++ das Überladen von Funktionen unterstützt, C++ jedoch nicht, da C++ beim Ändern der Funktion in ein Symbol den Parametertyp hinzufügt, C++ jedoch nicht gleich, solange die Parameter unterschiedlich sind und ihre symbolischen Namen nicht in Konflikt geraten. Wir können diese Beziehung zwischen Variablennamen und Symbolen anhand des folgenden Beispiels überprüfen.
/ * filename : test.cpp */ #include <stdio.h> namespace myname { int var = 42; } extern int _ZN6myname3varE; int main() { printf("%d\n", _ZN6myname3varE); return 0; }
Hier definieren wir die globale Variable var im Namespace, sie wird als Symbol _ZN6myname3varE geändert, und dann deklarieren wir manuell die externe Variable _ZN6myname3varE und drucken sie aus. Beim Kompilieren und Ausführen entspricht sein Wert genau dem Wert von var
$ gcc test.cpp -o test -lstdc++ $ ./test 42
extern „C“
Mit dem Symbolkonzept ist es für uns einfach, die Verwendung von extern zu betrachten. C“
extern "C" { int func(int); int var; }
Es bedeutet, den Compiler anzuweisen, den Code in Klammern nach dem externen „C“ als C-Code zu behandeln. Natürlich können wir auch
extern "C" int func(int); extern "C" int var;
in a deklarieren einzelne Anweisung Dies deklariert func und var vom Typ C. Oft schreiben wir eine Header-Datei, um einige C-Sprachfunktionen zu deklarieren, und diese Funktionen können von C- und C++-Code aufgerufen werden. Wenn wir sie aus C++-Code aufrufen, müssen wir der Header-Datei ein externes „C“ hinzufügen, andernfalls bei C++ Beim Kompilieren wird das Symbol nicht gefunden und das externe „C“ kann nicht hinzugefügt werden, da C eine solche Syntax nicht unterstützt. Nehmen wir als Beispiel die C-Bibliotheksfunktion memset
#ifdef __cplusplus extern "C" { #endif void *memset(void*, int, size_t); #ifdef __cplusplus } #endifUnter anderem ist __cplusplus ein vom C++-Compiler definiertes Makro. Wenn dieser Code mit C++ kompiliert wird, wird Memset in externem „C“ deklariert Da __cplusplus nicht definiert ist, liegt kein Syntaxfehler vor. Diese Technik wird häufig in System-Header-Dateien verwendet.