C++ Core Guidelines: C und C++ gleichzeitig verwenden

Modernes C++  –  36 Kommentare

Dank der gemeinsamen Geschichte von C und C++ sind beide Programmiersprachen sehr ähnlich. Da aber keine der Sprachen eine Teilmenge der anderen ist, gilt es, ein paar Regeln zu beachten, wenn sie gleichzeitig verwendet werden sollen.

Dieses Kapitel in den C++ Core Guidelines heißt: C-style Programming. Ehrlich gesagt, war mein erster Gedanke, nachdem ich es überflogen hatte, es zu ignorieren. Ein paar Gedanken später entschied ich mich aber, darüber zu schreiben. Zwei Gründe dafür möchte ich nennen:

  1. Das ist eine typische Herausforderung, wenn wir Legacy-Software pflegen.
  2. Ein Leser fragt mich explizit, ob ich den nicht mehr über Legacy-Software schreiben könne.

Hier sind die Regeln für heute:

Die erste Regel ist ein Selbstläufer, denn es geht in den Guidelines um C++.

CPL.1: Prefer C++ to C

Ohne Worte meinerseits die Begründung zu der Regel aus den C++ Core Guidelines: "C++ provides better type checking and more notational support. It provides better support for high-level programming and often generates faster code."

CPL.2: If you must use C, use the common subset of C and C++, and compile the C code as C++

Die erste Frage, die es zu beantworten gilt, wenn C und C++ gleichzeitig eingesetzt werden sollen, ist: Kannst du den ganzen Code mit einem C++ Compiler übersetzen? Beginnen möchte ich mit der positiven Antwort.

Der ganze Sourccode steht zur Verfügung

Das ist gut, denn du bist fast fertig. Fast, denn C ist keine Teilmenge von C++. Hier ist ein kleines, schlechtes C-Programm, dass sich mit einem C++ Compiler nicht übersetzen lässt.

// cStyle.c

#include <stdio.h>

int main(){

double sq2 = sqrt(2); // (1)

printf("\nsizeof(\'a\'): %d\n\n", sizeof('a')); // (2)

char c;
void* pv = &c;
int* pi = pv; // (3)

int class = 5; // (4)

}

Zuerst möchte ich das Programm mit dem C90 Standard übersetzen und ausführen.

Von ein paar Warnungen abgesehen, geht alles gut.

Das Programm cStyle.c besitzt ein paar Probleme. Es gibt keine Deklaration für die sqrt-Funktion (Zeile 2), die Zeile (3) führt eine implizite Konvertierung von einem void-Zeiger auf einen int-Zeiger durch und die Zeile (4) verwendet das Schlüsselwort class.

Jetzt bin ich auf die Meinung des C++-Compilers gespannt.

Ich erhalte, was ich verdiene: drei Compiler-Fehler. Das Programm cStyle.c zeigt noch einen viel subtileren Unterschied zwischen einem C- und C++-Compiler. Dazu habe ich das Programm auf die Zeile (2) reduziert: printf("\nsizeof(\'a\'): %d\n\n", sizeof('a'));. Hier ist die Ausgabe.

Anstelle von 4 wie bei dem C-Compiler, ist sizeof('a') mit dem C++-Compiler 1: 'c' ist ein int in C.

Nun kommt die herausfordernde Aufgabe.

Der ganze Sourcecode steht nicht zur Verfügung

Dies sind die wichtigen Punkte, die es zu beachten gilt.

  1. Verwende deinen C++ Compiler, um deine main Funktion zu übersetzen. Im Gegensatz zum C-Compiler, erzeugt der C++-Compiler zusätzlichen Startup-Code, der vor der main-Funktion ausgeführt wird. Zum Beispiel ruft dieser Startup-Code Konstruktoren von globalen (statischen) Objekten auf.
  2. Verwende deinen C++-Compiler, um dein Programm zu linken. Wenn der C++-Compiler zum Linken verwenden wird, bindet er automatisch die Standard-C++-Bibliothek hinzu.
  3. Verwende einen C- und C++-Compiler vom selben Anbieter, der dieselbe Aufrufkonvention (calling convention) verwenden sollte. Die Aufrufkonvention spezifiziert die Art und Weise, wie der Compiler auf Funktionen zugreift. Dies beinhaltet, in welcher Reihenfolge die Parameter allokiert werden, wie die Parameter übergeben werden oder ob der Aufrufer oder Aufgerufene sich um den Stack kümmert. Die Details zu x86s Aufrufkonvention lassen sich schön auf Wikipedia nachlesen.

CPL.3: If you must use C for interfaces, use C++ in the calling code using such interfaces

Im Gegensatz zu C unterstützt C++ das Überladen von Funktionen. Das heißt, dass sich Funktionen mit dem selben Namen aber verschiedenen Parametern definieren lassen. Der Compiler sucht sich einfach die passenden Funktion heraus, wenn sie aufgerufen wird:

// functionOverloading.cpp

#include <iostream>

void print(int) {
std::cout << "int" << std::endl;
}

void print(double) {
std::cout << "double" << std::endl;
}

void print(const char*) {
std::cout << "const char* " << std::endl;
}

void print(int, double, const char*) {
std::cout << "int, double, const char* " << std::endl;
}


int main() {

std::cout << std::endl;

print(10);
print(10.10);
print("ten");
print(10, 10.10, "ten");

std::cout << std::endl;

}

Die Ausgabe ist erwartungsgemäß:

Die entscheidende Frage ist aber nun: Wie kann der Compiler die verschiedenen Funktionen unterscheiden? Der C++-Compiler encodiert zusätzlich die Typen der Parameter in den Funktionsnamen. Dieser Prozess nennt sich Name Mangling. Name Mangeling ist nicht standardisiert und wird von jedem C++-Compiler spezifisch umgesetzt. Hin und wieder wird Name Mangling auch als Name Decoration bezeichnet.

Mit der Hilfe des Programms functionOverloading.cpp auf dem compiler explorer ist es einfach, dass Name Mangling in Aktion zu zeigen. Dazu ist es notwendig, den Button Demangle zu deaktivieren.

Hier sind die Namen, die der GCC 8.3 und der MSVC 19.16 erzeugen.

Durch den extern "C" Link-Spezifizierer weist du den C++ Compiler an, kein Name Mangling zu verwenden.

Indem du eine Funktion mittels extern "C" in deinem Code deklarierst, kannst du eine C-Funktion von C++ oder eine C++-Funktion von C aufrufen.

extern "C" lässt sich explizit für jede Funktion,

extern "C" void foo(int);

für jede Funktion in einem Bereich

extern "C" {
void foo(int);
double bar(double);
};

oder auf eine ganze Headerdatei mit Include Guards anwenden. Das Makro __cplusplus gibt es nur, wenn der C++-Compiler verwendet wird:

#ifdef __cplusplus
extern "C" {
#endif
void foo(int);
double bar(double);
.
.
.
#ifdef __cplusplus
}
#endif

Wie geht es weiter?

Ich freue mich sehr, dass mit dem nächsten Artikel eine Miniserie zu CppInsight auf meinem Blog beginnt. CppInsight ist ein großartiges Werkzeug, das ich in meinen Artikeln und Schulungen intensiv verwende, um die Magie des C++-Compilers ans Tageslicht zu bringen. Dem Werkzeug fehlt jedoch eine gute Einführung. Wer kann die Einführung besser schreiben als Andreas Fertig, der Autor von CppInsight?