Die 12 häufigsten Programmierfehler

Martin Kompf

Immer wieder werden die gleichen Fehler gemacht... Diese Liste enthält die zwölf am häufigsten vorkommenden Fehler in der C-Programmierung und soll dabei helfen, selbige zu vermeiden.

Die Liste ist eine Übersetzung und Erweiterung der englischen Originalversion von Jeppe Cramon.

  1. Anfang und Ende von Kommentaren müssen korrekt bezeichnet sein.
    Kommentaranfang: /*
    Kommentarende: */
  2. Der Zuweisungsoperator = wird oft mit dem Testoperator == verwechselt.
    Dieser Fehler tritt insbesondere deswegen so häufig auf, weil andere Programmiersprachen = auch als Testoperator verwenden. Außerdem entdeckt der Compiler diesen Fehler oftmals nicht, da eine Zuweisung einen Wert hat und demzufolge in einem Vergleichsausdruck vorkommen kann. Zum Beispiel:
    if (number = 9) { /* falsch */
        number = 0;
    }
    
    Diese Sequenz ist syntaktisch korrekt, compiliert fehlerfrei, wird jedoch bei der Abarbeitung nicht das gewünschte tun. Anstatt der Variablen number den Wert 0 zuzuweisen, falls diese gleich 9 ist, wird number immer auf 0 gesetzt, da die Zuweisung number = 9 immer den logischen Wert true zurückliefert!
    Abhilfe kann man schaffen, indem man sich angewöhnt, immer zuerst den Wert, gegen den verglichen wird, hinzuschreiben. Also:
    if (9 == number) { /* richtig */
        number = 0;
    }
    
    Wird hier statt == aus Versehen = geschrieben, so wird schon der Compiler eine Fehlermeldung ausgeben.
  3. Fehler bei Zuweisungen in Vergleichsausdrücken.
    Werden die Klammern um eine Zuweisung vergessen, wird zuerst der Vergleich abgearbeitet und sein Ergebnis zugewiesen. Dies ist syntaktisch korrekt und wird vom Compiler nicht bemerkt. Zum Beispiel:
    while (ch = getchar() != EOF) /* falsch */
    
    Dies wird der Variablen ch den Wert 1 für TRUE zuweisen bis getchar() EOF zurückliefert. Dann erhält ch den Wert 0 für FALSE.
    Wahrscheinlich wollte der Programmierer jedoch, daß der Variablen ch das von getchar() zurückgelieferte Zeichen zugewiesen wird! Die korrekte Syntax dafür ist:
    while ((ch = getchar()) != EOF) /* richtig */
    
  4. Fehler bei der Benutzung von Arrays.
    Der kleinste Index eines Arrays in C ist 0. Das heißt, die Deklaration
    int a[10];
    
    definiert zehn Feldelemente a[0], a[1], ..., a[9]. Ein Versuch, auf a[10] zuzugreifen, wird weder vom Compiler noch vom Laufzeitsystem bemerkt:
    for (i=0; i <= 10; i++) /* falsch! Der höchste Index ist 9 */
        a[i] = i;
    
    Dieses Beispiel wird zur Laufzeit der Variablen a[10] den Wert 10 zuweisen. Da es die Variable a[10] jedoch nicht gibt, wird unter Umständen eine andere Variable überschrieben.
    Das Fehlen der Überprüfung von Feldgrenzen zur Laufzeit wird oft von Hackern zum Erzeugen (un-)kontrollierter Programmabstürze ausgenutzt und stellt bei Netzwerksoftware ein kritisches Sicherheitsloch dar. Entdecken lassen sich solche Fehler mit speziellen Memory Debuggern, wie Dmalloc oder Bounds Checker.
  5. Klammern beim Aufruf von Funktionen ohne Parameter.
    Falls zum Beispiel eine parameterlose Funktion getValue existiert, so ist der Funktionsaufruf
    x = getValue; /* falsch */
    
    falsch.
    Der korrekte Aufruf wäre
    x = getValue(); /* richtig */
    
  6. Fehler bei der Übergabe eines Pointers an eine Funktion.
    Eine Funktion convert ist als
    void convert(int *px);
    
    deklariert. Da der Parameter px ein Pointer auf eine Integer Variable ist, muß auch der Funktionsaufruf mit einem Pointer als Parameter erfolgen. Also zum Beispiel:
    int result;
    convert(&result);
    
    Ein typischer Fehler ist, daß der Adreßoperator & hier vergessen wird.
  7. Funktionen müssen deklariert werden!
    Wird eine Funktion nicht vor ihrer Benutzung deklariert, so wird automatisch int als Rückgabetyp angenommen. Zum Beispiel:
    double a;
    a = getdouble(); /* falsch */
    
    Hier wird a ein int anstatt des erwarteten double zugewiesen, da der Compiler annimmt, daß getdouble() ein int zurückliefert. Um dies zu korrigieren, muß die Funktion zunächst deklariert werden (dies wird auch als Prototype bezeichnet):
    double getdouble();
    double a;
    
    a = getdouble(); /* richtig */
    
  8. Das 'break' in 'switch' Anweisungen darf nicht vergessen werden!
    Wird das break vergessen, so wird die Programmausführung bis zum nächsten break oder dem Ende der switch Anweisung fortgesetzt. Beispiel:
    switch (weekday) {
      case MONDAY : printf("Start a new week");
      case TUESDAY:         /* no break here */
      case FRIDAY :
        workday++;
        break;
      default: weekday++;
    }
    
    Ist weekday gleich MONDAY, so wird ebenfalls der Code für TUESDAY und FRIDAY ausgeführt, da nach MONDAY und TUESDAY kein break steht. Ist dieses Verhalten vom Programmierer beabsichtigt, so sollte es wie im Beispiel kommentiert werden.
  9. Benutzung von Seiteneffekten in Ausdrücken.
    Die Benutzung von Seiteneffekten ("um Schreibarbeit zu sparen") kann zu ungewollten Ergebnissen führen. Beispiel:
    a[n] = n++;
    
    Ist equivalent entweder zu:
    a[n] = n;
    n = n + 1;
    
    oder zu:
    a[n + 1] = n;
    n = n + 1;
    
    Welcher der beiden Fälle eintritt, hängt vom Compiler ab.
  10. Fehler bei Typkonvertierungen.
    Die Konvertierung von char nach int hängt ähnlich wie Bitshiftoperationen auf char davon ab, ob der Typ char als signed oder unsigned definiert ist. Dies wiederum hängt vom Compiler oder einem Compilerschalter ab. Beispiel:
    char a;
    
    a = 228;            /* iso code for the german a-umlaut */
    printf( "%d\n", a); /* implicit conversion to int */
    
    Es wird entweder -28 oder 228 ausgegeben. Beim GNU Compiler gcc läßt sich dieses Verhalten durch Verwenden der Kommandozeilenschalter -funsigned-char oder -fsigned-char steuern.
  11. Klammern beim Verwenden von Pointern und Increment/Decrement Operatoren.
    Beispiel:
    char line[80];
    *lp = line;
    ch = *lp++;         /* the same as *(lp++) */
    
    In diesem Beispiel wird ch das Zeichen zugewiesen, auf welches lp zeigt. Anschließend wird lp inkrementiert. Verwendet man jedoch
    ch = (*lp)++;
    
    so wird ch ebenfalls das Zeichen zugewiesen, auf welches lp zeigt. Anschließend wird jedoch nicht lp, sondern das Zeichen, auf welches lp zeigt, inkrementiert!
  12. Die logischen Operatoren && und || dürfen nicht mit den Bitoperatoren & und | verwechselt werden!