C++ ist eine Mehrzweckprogrammiersprache, die mehrere Aspekte vereint: Objektorientierung, imperative, funktionale und generische Programmierung. Gleichzeitig ist C++ geprägt von der Vorgängersprache C und bietet viele Freiheiten. Diese Freiheiten führen zu manchmal drastischen, manchmal subtilen Fehlern. Die mit C++11 hinzugekommenen Erweiterungen mitsamt einigen Nachbesserungen und Ergänzungen (C++14) ermöglichen es, mit wenig Aufwand einige der Fehler zu vermeiden, wie die folgenden zwei Beispiele zeigen.

Speicherlecks und verwitwete Zeiger

Ein Speicherleck (engl. memory leak) entsteht dann, wenn zwar mit new Speicher beschafft, aber nicht freigegeben wird. Ursache ist eine fehlende delete-Anweisung, oder eine delete-Anweisung, die zum Beispiel aufgrund einer Exception nicht erreicht werden kann. Bei einem rund um die Uhr laufenden Programm kann dadurch nach und nach der Speicher volllaufen, sodass am Ende nichts mehr geht.

Verwitwete Zeiger (engl. dangling pointer) sind Zeiger, denen eine gültige Adresse auf dem Heap zugewiesen war, denen jedoch das Objekt abhanden gekommen ist, etwa durch ein vom Destruktor des Objekts ausgeführtes delete. Eine Derefenzierung so eines Zeigers kann zum Absturz führen.

Beide Fehler lassen sich leicht vermeiden, indem die in C++11 eingeführten „smart pointer“-Klassen shared_ptr und unique_ptr verwendet werden. Ein Objekt der ersten verwaltet mehrere Verweise auf ein Objekt, ein Objekt der zweiten zeigt auf nur eins. Wesentliche Eigenschaft von Objekten beider Klassen ist, dass deren Destruktor letztlich für die Löschung der damit verbundenen Heap-Objekte sorgt, auch im Falle einer Exception. Die Benutzung wird durch die Funktionen make_shared() (C++11) und make_unique() (C++14) erleichtert.

Fehlerhafte automatische Typumwandlung

Sehen wir uns das folgende, unschuldig wirkende Beispiel eines Programmfragments an, das die Position des ersten Punkts in einem zu durchsuchenden Text finden soll. Dabei ist string::npos der Wert, der zurückgegeben wird, wenn nichts gefunden wird. npos ist typischerweise die maximal mögliche unsigned-Zahl.

 

string zudurchsuchenderText("ein Text");
unsigned int punktPosition = zudurchsuchenderText.find(".");
if(punktPosition == string::npos) {
cout << "'.' nicht gefunden!\n";
}

 

Das mag auf einem 32-Bit-System funktionieren, auf dem der Typ von npos derselbe Typ wie unsigned int ist. Auf einem 64-Bit-System jedoch könnte unsigned int eine 32-Bit-Zahl sein und npos eine 64-Bit-Zahl. Das heißt, auf so einem 64-Bit-System wäre die Bedingung möglicherweise false, obwohl der zu durchsuchende Text keinen Punkt enthält. Der Grund ist die automatische Typumwandlung, die der Compiler bei der Zuweisung vornimmt. Dabei werden ggf. die höchstwertigen Bits von npos abgeschnitten.

Der tatsächliche Typ von npos ist string::size_type. Der Datentyp size_t ist groß genug, und wenn der Typ von size_t nicht derselbe wie string::size_type sein sollte, gibt es auf jeden Fall eine definierte Typumwandlung. Noch besser ist es aber, den Compiler mit auto (C++11) den richtigen Typ feststellen zu lassen:

 

auto punktPosition = zudurchsuchenderText.find(".");

Dieser Programmcode funktioniert gleichermaßen auf einem 32- und auf einem 64-Bit-System. Das Beispiel ist dem Kapitel 21 des Buchs „Der C++-Programmierer“ entnommen.

 

Sie wollen ein Buchexemplar Der C++-Programmierer gewinnen?

Schreiben Sie uns bis zum 23. Juni, warum Sie das Buch gewinnen möchten, drei Exemplare werden unter den Teilnehmern der Aktion verlost!

Beachten Sie dabei bitte unsere Gewinnspielteilnahmebedingungen!

 Das Gewinnspiel ist beeendet! Kevin und Pascal dürfen sich auf ihr Buchexemplar freuen! Wir melden uns per E-Mail bei den beiden Gewinnern! [nachträglich eingefügt, Hanser Update Redaktion]

Lesen Sie auch die „5 Fragen – 5 Antworten mit Ulrich Breymann“ zu C++.