4 C++-Programmierfehler die schwer zu finden sind

Einen nicht unerheblichen Anteil an der Arbeit eines Programmierers machen das Finden und Beseitigen von Fehlern aus. Dabei gilt der Grundsatz, dass man Fehler möglichst früh im Entwicklungsprozess finden sollte. Noch besser ist es natürlich die Fehler überhaupt gleich zu vermeiden. Dieser Artikel zeigt vier typische Fehler, die vom Compiler in der Regel nicht erkannt werden und deshalb schwierig zu finden sind. Wenn du diese Fehler noch nicht kanntest, wirst du nach dem Lesen dieses Artikels wieder ein klein wenig besser programmieren können.

Damit ein Fehler es überhaupt erst einmal zu einem Laufzeitfehler schafft, muss er vom Compiler unentdeckt bleiben. Hilf' dem Compiler indem du die Warnstufe (Warning Level) möglichst hoch einstellst. Warnungen vom Compiler können zwar unangenehm sein aber noch viel unangenehmer ist ein subtiler Laufzeitfehler. Warnungen ignorieren solltest du wirklich nur dann, wenn du einen sehr schlüssigen Grund dafür hast.

Nachfolgend findest du vier Beispiele für typische C++-Fehler die vom Compiler unter Umständen nicht erkannt werden. Am besten probierst du diese Beispiele auch gleich mal in deiner Entwicklungsumgebung aus, damit du siehst, ob dein Compiler dich vor dem einen oder anderen Fehler warnen würde.

Variable nicht initialisiert

In C++ werden Variablen beim Anlegen nicht automatisch initialisiert. Wenn du mit einer nicht initialisierten Variable arbeitest, ist das Verhalten des Programms undefiniert. In folgendem Beispiel kann die Variable counter einen beliebigen Wert haben, der mit großer Wahrscheinlichkeit größer 10 ist. Wahrscheinlich wird deshalb die while-Schleife gar nicht erst betreten. Das Programm wird sich in jedem Fall nicht so verhalten, wie das ursprünglich beabsichtigt war.

    int counter;

    while(counter < 10)
    {
        cout << counter;
        ++counter;
    }

Das nachfolgende Beispiel zeigt den korrigierten Code. Der Integer wird explizit mit 0 initialisiert und es werden die Ziffern 0 bis 9 in der Konsole angezeigt.

    int counter = 0;

    while(counter < 10)
    {
        cout << counter;
        ++counter;
    }

Wenn du bereits objektorientiert programmierst, solltest du wissen, dass dies auch für Membervariablen gilt. Dafür verwendet man üblicherweise die Initialisierungsliste.

In C++ werden Variablen nicht automatisch initialisiert, deshalb musst du jeder Variable auch selbst einen Initialwert zuweisen.

Verwechslung von Zuweisungsoperator und Vergleichsoperator (=/==)

Der Zuweisungsoperator wird verwendet um einer Variablen oder einem Objekt einen bestimmten Wert zuzuweisen, returniert wird normalerweise eine Referenz auf das Objekt, dem zugewiesen wurde. Der Vergleichsoperator wird verwendet um zwei Variablen oder Objekte miteinander zu vergleichen, das Ergebnis ist ein Boolean, der aussagt ob die verglichenen Objekte gleich sind. Eine Verwechslung kann gravierende Folgen haben, wie das nachfolgende Beispiel verdeutlicht.

Anstatt zu überprüfen, ob der Wert der Variable counter 5 ist wird hier der Variable der Wert 5 zugewiesen. Die Bedingung ist true, weil der Rückgabewert der Zuweisung 5 entspricht und ein Integer ungleich 0 als true ausgewertet wird. Da dies in jedem Schleifendurchlauf passiert, wird die Abbruchbedingung (counter < 10) nie erreicht und das Programm läuft in einer Endlosschleife, die auf der Konsole zyklisch "5" ausgibt.

    int counter = 0;

    while(counter < 10)
    {
        // do some stuff here...
        // ...

        // here is a quick check
        if(counter = 5)
        {
            cout << counter;
        }

        ++counter;
    }

Im Gegensatz dazu wird in folgendem korrekten Beispiel nur ein einziges mal "5" ausgegeben, so wie vom Programmierer gewünscht.

    int counter = 0;

    while(counter < 10)
    {
        // do some stuff here...
        // ...

        // here is a quick check
        if(counter == 5)
        {
            cout << counter;
        }

        ++counter;
    }

Achte also stets darauf, bei Vergleichen auch tatsächlich zwei "=", also den Vergleichsoperator zu verwenden.

Kein break in switch-Anweisung

Die switch-Anweisung dient zur übersichtlichen Fallunterscheidung basierend auf ganzzahligen Werten. Ein typischer Programmierfehler der hier gerne passiert ist das Vergessen des breaks. Stimmt eine case-Konstante mit dem abgefragten Wert überein, werden alle folgenden Anweisungen ausgeführt, bis ein break kommt. Nachfolgendes Beispiel zeigt, wie es nicht gedacht ist:

    // get random number between 1 and 10
    int myNumber = rand() % 10 + 1;

    // check if this is an important number to me
    switch(myNumber)
    {
        case 1: cout << "Meine liebste Zahl wurde gezogen!";
        case 3: cout << "Meine zweitliebste Zahl wurde gezogen!";
        default: cout << "Eine für mich unbedeutende Zahl wurde gezogen.";
    }

Wenn hier der Zufallsgenerator 1 auswählt, werden alle drei Sätze ausgegeben, da die case-Abschnitte kein abschließendes break haben. Wenn man sich dessen bewusst ist, kann man das auch nutzen um eine Oder-Verknüpfung abzubilden, was allerdings schon eher als fortgeschrittene Technik betrachtet werden kann.

    // get random number between 1 and 10
    int myNumber = rand() % 10 + 1;

    // check if this is an important number to me
    switch(myNumber)
    {
        case 1:
            cout << "Meine liebste Zahl wurde gezogen!";
            break;
        case 3:
            cout << "Meine zweitliebste Zahl wurde gezogen!";
            break;
        default:
            cout << "Eine für mich unbedeutende Zahl wurde gezogen.";
            break;
    }

Hier wurden die breaks korrekt gesetzt und der Code macht was er sollte. Um Fehler wie oben zu vermeiden kannst du dir angewöhnen, jedes case und default unmittelbar mit einem break zu versehen, bevor der tatsächliche Code geschrieben wird. Mit folgender Faustregel für Anfänger kann dir dieser Fehler nicht passieren.

Setze innerhalb einer switch-Anweisung zu jedem case und zum default sofort ein break. Entferne es nur, wenn du dir sicher bist, was dann passiert.

Integer Division

Ein klassisches numerisches Problem kann auftreten, wenn du eine Division mit Integer-Werten machst. Wenn sowohl Dividend als auch Divisor Integer sind ist nämlich das Ergebnis auch ein Integer und das bedeutet, dass der Nachkommaanteil abgeschnitten wird. Auch wenn du das Ergebnis einer Gleitkomma-Variable zuweist ändert das nichts daran:

    int dividend = 6;
    int divisor = 4;

    double result = dividend / divisor;
    cout << result; // result is 1.0!

Die Variable result enthält hier wider Erwarten 1.0, da das Ergebnis der Division schon ein Integer ist, der stillschweigend auf einen double umgewandelt ("gecastet") wird. Es gibt verschiedene Möglichkeit um dieses Problem zu umgehen. Eine davon ist die Verwendung von double oder float für Dividend und Divisor:

    double dividend = 6;
    double divisor = 4;

    double result = dividend / divisor;
    cout << result; // result is 1.5

Solltest du Dividend und Divisor nur als Integer zur Verfügung haben, musst du nicht extra eine Zwischenvariable anlegen. Hier reicht es einfach einen der beiden Werte ausdrücklich (explizit) auf double oder float zu casten:

        int dividend = 6;
        int divisor = 4;

        double result = static_cast<double>(dividend) / divisor;
        cout << result; // result is 1.5

In diesem Beispiel ist schon das Ergebnis der Division ein double und somit gehen keine Nachkommastellen mehr bei der Zuweisung an result verloren.

Fazit

Du hast jetzt 4 typische Programmierfehler in C++ kennengelernt. Sogar in professionellem Umfeld ist die Wahrscheinlichkeit auf einen dieser Fehler zu treffen gerade bei umfangreicheren Codebasen verhältnismäßig groß. Wie du gesehen hast, lassen sich solche Fehler aber sehr leicht vermeiden.

Datentyp Bool

Ein Bool (oder auch Boolean) steht für einen Wahrheitswert, der entweder true also wahr oder false also unwahr sein kan

Variablen sind eines der wichtigsten Elemente einer Programmiersprache.

Ein Byte mit 8 Bit

In vielen Programmiersprachen hat der Programmierer eigentlich gar nichts mehr mit Speicherverwaltung und Bits & Bytes zu tun.

Copyright © 2014 www.lerneprogrammieren.com