C ++ und C kombinieren - wie funktioniert #ifdef __cplusplus?

318

Ich arbeite an einem Projekt, das viel alten C- Code enthält. Wir haben mit dem Schreiben in C ++ begonnen, mit der Absicht, den Legacy-Code schließlich auch zu konvertieren. Ich bin ein wenig verwirrt darüber, wie C und C ++ interagieren. Ich verstehe, dass durch das Umschließen des C- Codes mit extern "C"dem C ++ - Compiler die Namen des C- Codes nicht entstellt werden, aber ich bin mir nicht ganz sicher, wie ich dies implementieren soll.

Also, oben in jeder C- Header-Datei (nach den Include-Wachen) haben wir

#ifdef __cplusplus
extern "C" {
#endif

und unten schreiben wir

#ifdef __cplusplus
}
#endif

Dazwischen haben wir alle unsere Includes, Typedefs und Funktionsprototypen. Ich habe ein paar Fragen, um zu sehen, ob ich das richtig verstehe:

  1. Wenn ich eine C ++ - Datei A.hh habe, die eine C- Header-Datei Bh enthält, eine andere C- Header-Datei Ch enthält, wie funktioniert das? Ich denke, wenn der Compiler in Bh eintritt, __cpluspluswird er definiert, so dass er den Code umschließt extern "C" (und __cplusplusnicht in diesem Block definiert wird). Wenn es also in Ch eintritt, __cpluspluswird es nicht definiert und der Code wird nicht eingewickelt extern "C". Ist das richtig?

  2. Ist etwas falsch daran, ein Stück Code damit zu verpacken extern "C" { extern "C" { .. } }? Was wird der zweite extern "C" tun?

  3. Wir legen diesen Wrapper nicht um die .c-Dateien, sondern nur um die .h-Dateien. Was passiert also, wenn eine Funktion keinen Prototyp hat? Hält der Compiler es für eine C ++ - Funktion?

  4. Wir verwenden auch Code von Drittanbietern, der in C geschrieben ist und keinen solchen Wrapper enthält. Jedes Mal, wenn ich einen Header aus dieser Bibliothek einbinde, habe ich ein extern "C"#include eingefügt. Ist das der richtige Weg, um damit umzugehen?

  5. Ist dies eine gute Idee? Gibt es noch etwas, was wir tun sollten? Wir werden auf absehbare Zeit C und C ++ mischen , und ich möchte sicherstellen, dass wir alle unsere Grundlagen abdecken.

dublev
quelle
2
Kurz gesagt, dies ist die beste Erklärung: To ensure that the names declared in that portion of code have C linkage, and thus C++ name mangling is not performed. (Ich habe es aus dem Link )
anhldbk
Sie müssen den Namen der C-Sprache nicht fett
Edward Karak

Antworten:

290

extern "C"ändert nicht wirklich die Art und Weise, wie der Compiler den Code liest. Wenn sich Ihr Code in einer .c-Datei befindet, wird er als C kompiliert. Wenn er sich in einer .cpp-Datei befindet, wird er als C ++ kompiliert (es sei denn, Sie tun etwas Seltsames an Ihrer Konfiguration).

Was extern "C"sich auswirkt, wirkt sich auf die Verknüpfung aus. Wenn C ++ - Funktionen kompiliert werden, werden ihre Namen entstellt - dies ermöglicht eine Überladung. Der Funktionsname wird basierend auf den Typen und der Anzahl der Parameter geändert, sodass zwei Funktionen mit demselben Namen unterschiedliche Symbolnamen haben.

Code in einem extern "C"ist immer noch C ++ - Code. Es gibt Einschränkungen, was Sie in einem externen "C" -Block tun können, aber es geht nur um die Verknüpfung. Sie können keine neuen Symbole definieren, die nicht mit C-Verknüpfung erstellt werden können. Das heißt zum Beispiel keine Klassen oder Vorlagen.

extern "C"Blöcke nisten schön. Es gibt auch extern "C++"Fälle, in denen Sie hoffnungslos in extern "C"Regionen gefangen sind , aber aus Sicht der Sauberkeit ist dies keine so gute Idee.

Nun speziell zu Ihren nummerierten Fragen:

Zu # 1: __cplusplus bleibt innerhalb von extern "C"Blöcken definiert . Dies spielt jedoch keine Rolle, da die Blöcke ordentlich verschachtelt sein sollten.

Zu # 2: __cplusplus wird für jede Kompilierungseinheit definiert, die über den C ++ - Compiler ausgeführt wird. Im Allgemeinen bedeutet dies, dass CPP-Dateien und alle Dateien, die in dieser CPP-Datei enthalten sind, enthalten sind. Dieselbe .h (oder .hh oder .hpp oder was-hast-du) könnte zu unterschiedlichen Zeiten als C oder C ++ interpretiert werden, wenn verschiedene Kompilierungseinheiten sie enthalten. Wenn die Prototypen in der .h-Datei auf C-Symbolnamen verweisen sollen, müssen sie extern "C"bei der Interpretation als C ++ vorhanden sein, und sie sollten extern "C"bei der Interpretation als C nicht vorhanden sein - daher die #ifdef __cplusplusÜberprüfung.

Um Ihre Frage 3 zu beantworten: Funktionen ohne Prototypen haben eine C ++ - Verknüpfung, wenn sie sich in CPP-Dateien und nicht in einem extern "C"Block befinden. Dies ist jedoch in Ordnung, da es, wenn es keinen Prototyp hat, nur von anderen Funktionen in derselben Datei aufgerufen werden kann und es Ihnen im Allgemeinen egal ist, wie die Verknüpfung aussieht, da Sie diese Funktion nicht planen sowieso von irgendetwas außerhalb derselben Kompilierungseinheit aufgerufen werden.

Für # 4 hast du es genau. Wenn Sie einen Header für Code mit C-Verknüpfung einfügen (z. B. Code, der von einem C-Compiler kompiliert wurde), müssen Sie extern "C"den Header verwenden. Auf diese Weise können Sie eine Verknüpfung mit der Bibliothek herstellen. (Andernfalls würde Ihr Linker nach Funktionen mit Namen _Z1hicsuchen, nach denen Sie gesucht habenvoid h(int, char)

5: Diese Art des Mischens ist ein häufiger Grund extern "C", und ich sehe nichts Falsches daran, es so zu machen - stellen Sie einfach sicher, dass Sie verstehen, was Sie tun.

Andrew Shelansky
quelle
10
Gut zu erwähnen, extern "C++"wenn Ihr C ++ - Header / Code tief in einem C-Code gefangen ist
deddebme
1
Ich habe ein einfaches C-Programm geschrieben. Darin habe ich den Block #ifdef __cplusplus hinzugefügt und einen printf hinzugefügt ("__ cplusplus defined \ n"); drin. Wenn ich es mit gcc kompiliere, wird "__cplusplus defined" nicht gedruckt, aber wenn ich es mit g ++ kompiliere, wird es gedruckt. Ich denke also, __cplusplus bedeutet, dass der Compiler ein C ++ - Compiler ist (Sie haben es gesagt). Ist es nicht richtig? (Weil ich gesehen habe, dass Sie gesagt haben, '__cplusplus sollte innerhalb externer "C" -Blöcke definiert werden'. Können wir __cplusplus explizit definieren?
Chan Kim
1
Während Sie sollten in der Lage zu definieren (fast) alles , was Sie wollen, den ganzen Punkt __cplusplusist , um zu bestimmen , ob C++vs benutzt wird C, ist es so definiert , manuell / trotzt ausdrücklich dem Zweck , es ...
nurchi
Beim externen "C" geht es in der Tat nicht darum, wie der Compiler die Quelldatei anzeigt, sondern darum, wie er die Header-Datei anzeigt. Strukturen können beim Kompilieren als C vs C ++ unterschiedliche Größen haben, es gibt natürlich den Namen Mangling und möglicherweise auch andere Unterschiede.
Nick
39
  1. extern "C"ändert das Vorhandensein oder Fehlen des __cplusplusMakros nicht. Es ändert lediglich die Verknüpfung und Namensverfälschung der umschlossenen Deklarationen.

  2. Sie können extern "C"Blöcke ziemlich glücklich verschachteln .

  3. Wenn Sie Ihre .cDateien als C ++ kompilieren, wird alles, was sich nicht in einem extern "C"Block befindet und keinen extern "C"Prototyp enthält, als C ++ - Funktion behandelt. Wenn Sie sie als C kompilieren, ist natürlich alles eine C-Funktion.

  4. Ja

  5. Auf diese Weise können Sie C und C ++ sicher mischen.

Anthony Williams
quelle
Wenn Sie .cDateien als C ++ kompilieren, wird alles als C ++ - Code kompiliert, auch wenn es sich in einem extern "C"Block befindet. Der extern "C"Code kann keine Funktionen verwenden, die von C ++ - Aufrufkonventionen abhängen (z. B. Überladen von Operatoren), aber der Hauptteil der Funktion wird weiterhin als C ++ kompiliert, mit allem, was dazu gehört.
David C.
21

Ein paar Fallstricke, die die hervorragende Antwort von Andrew Shelansky ergänzen und mit denen man ein wenig nicht einverstanden ist, ändern nichts an der Art und Weise, wie der Compiler den Code liest

Da Ihre Funktionsprototypen als C kompiliert sind, können dieselben Funktionsnamen nicht mit unterschiedlichen Parametern überladen werden - dies ist eines der Hauptmerkmale der Namensverknüpfung des Compilers. Es wird als Verknüpfungsproblem beschrieben, aber das ist nicht ganz richtig - Sie erhalten Fehler sowohl vom Compiler als auch vom Linker.

Die Compilerfehler treten auf, wenn Sie versuchen, C ++ - Funktionen der Prototypdeklaration zu verwenden, z. B. Überladen.

Die Linkerfehler auftreten werden später , weil Ihre Funktion erscheint nicht gefunden werden, wenn Sie nicht die haben extern „C“ Wrapper um Erklärungen und der Header wird in einer Mischung von C und C ++ Quelle enthält.

Ein Grund, Menschen davon abzuhalten, das Kompilierungs-C als C ++ - Einstellung zu verwenden, besteht darin, dass ihr Quellcode nicht mehr portierbar ist. Diese Einstellung ist eine Projekteinstellung. Wenn eine .c-Datei in ein anderes Projekt verschoben wird, wird sie nicht als c ++ kompiliert. Ich würde es vorziehen, wenn sich die Leute die Zeit nehmen, um Dateisuffixe in .cpp umzubenennen.

Andy Dent
quelle
1
Dies war die kryptische Ursache, die mir die Haare auszog. Muss wirklich irgendwo gepostet werden.
Mitchell Currie
3

Es geht um das ABI, damit sowohl C- als auch C ++ - Anwendungen problemlos C-Schnittstellen verwenden können.

Da die C-Sprache sehr einfach ist, war die Codegenerierung für verschiedene Compiler wie GCC, Borland C \ C ++, MSVC usw. über viele Jahre stabil.

Während C ++ immer beliebter wird, müssen der neuen C ++ - Domäne viele Dinge hinzugefügt werden (zum Beispiel wurde die Cfront bei AT & T endgültig aufgegeben, weil C nicht alle benötigten Funktionen abdecken konnte). Wie die Vorlagenfunktion und die Generierung von Code zur Kompilierungszeit aus der Vergangenheit haben die verschiedenen Compilerhersteller die eigentliche Implementierung von C ++ - Compiler und Linker tatsächlich separat durchgeführt. Die tatsächlichen ABIs sind auf verschiedenen Plattformen überhaupt nicht mit dem C ++ - Programm kompatibel.

Die Leute möchten das eigentliche Programm möglicherweise immer noch in C ++ implementieren, behalten aber weiterhin die alte C-Schnittstelle und ABI wie gewohnt bei. Die Header-Datei muss extern "C" {} deklarieren. Sie teilt dem Compiler mit, dass kompatibles / altes / einfaches / einfaches C-ABI generiert wird für die Schnittstellenfunktionen, wenn der Compiler C-Compiler ist, nicht C ++ - Compiler.

Bo Zhou
quelle