Navigation

Beliebte Fehler in C und C++

Willkürliche Sammlung von Fehlern in C und C++

Dies ist eine Sammlung von Fehlern, wie sie gerne in C oder C++ Programmen gemacht werden. Derzeit noch leer, aber wachsend:

1. Strings ohne Ende

1.1. Problem

strncpy dient dazu, nur die ersten n Zeichen eines Strings zu kopieren. Viele kommen daher auf die Idee, strncpy wie folgt zu verwenden:










10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
// Struktur definition
struct person {
    char vname[16]; // Vorname
    char nname[16]; // Nachname 
};

// Person erzeugen
struct person* create_person(const char* vname, const char* nname) {

    struct person* p = malloc(sizeof(*p));

    if (p) {
        strncpy(p->nname, nname, 16);
        strncpy(p->vname, vname, 16);
    }
    return p;
}

// Person ausgeben
void print_person(const struct person* p) {
    printf("%s, %s\n, p->nname, p->vname);
}

Die Routine create_person soll also ein struct person Objekt erzeugen; die Werte für den Vor- und Nachnamen werden übergeben und mit Hilfe von strncpy kopiert, eben um zu verhindern, daß zu lange Namen ungewollt Speicher überschrieben. Die gute Nachricht ist, daß dies von strncpy auch tatsächlich geleistet wird. Die schlechte Nachricht ist die, daß strncpy es nicht unbedingt so macht, wie der unbedarfte Programmierer erwartet: Nehmen wir an, jemand hat den Vornamen "Sabine" (vname) und den Nachnamen "Leutheusser-Schnarrenberger" (nname) an create_persion übergeben und will nun mittels print_person die Informationen zur Person ausgeben, so wäre folgende Ausgabe möglich (dies hängt vom Speicheralignment ab):

Leutheusser-SchnSabine, Sabine

Das ist sicherlich nicht das, was wir wollten, denn eigentlich ist die Intention, Vor- und Nachnamen jeweils nach dem 15. Zeichen abzuschneiden (beachte: ein 15 Zeichen langer String braucht 16 Byte, weil stets das Terminierende 0 Byte angehängt werden muß). Wir sehen aber, daß die ersten 16 Zeichen des Nachnamens kopiert wurden. Da das 0 Byte nicht kopiert wurde, geht der String direkt mit dem Vornamen weiter, der ja direkt dahinter im Speicher folgt.

Das Problem ist, daß strncpy den String ggf. nicht terminiert (sprich: kein 0 Byte anhängt), nämlich genau dann, wenn der String länger oder gleich lang wie der Zielspeicherbereich ist.

1.2. Lösungsalternativen

1.2.1. Den String manuell terminieren

Die wohl einfachste Variante besteht darin, den String manuell zu terminieren. Der folgende Source zeigt die Herangehensweise (wir betrachten nur die Funktion create_persion):










10 
11 
12 
13 
// Person erzeugen
struct person* create_person(const char* vname, const char* nname) {

    struct person* p = malloc(sizeof(*p));

    if (p) {
        strncpy(p->nname, nname, 16);
        p->nname[15] = ;
        strncpy(p->vname, vname, 16);
        p->vname[15] = ;
    }
    return p;
}

1.2.2. strncat verwenden

Die andere Vorgehensweise macht deutlich, daß die Standardlibrary nicht immer konsequent entworfen worden ist. Denn strncat hat nicht das Problem, daß es Strings. ggf nicht terminiert. Allerdings muß man eben sicher stellen, daß der Speicher irgendwie initialisiert ist, was hier nun mit der Verwendung von calloc erreicht wird:










10 
11 
// Person erzeugen
struct person* create_person(const char* vname, const char* nname) {

    struct person* p = calloc(sizeof(*p)1);

    if (p) {
        strncat(p->nname, nname, 16);
        strncat(p->vname, vname, 16);
    }
    return p;
}

2. Zeitmaschine

2.1. Problem

Gegeben sei folgendes harmloses Progrämmchen, was den Benutzer 10 Sekunden warten läßt:










10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
#include <stdio.h>
#include <time.h>
#include <unistd.h> // Wegen "sleep".

int main() {
    struct tm* start;
    struct tm* end;
    time_t tstart;
    time_t tend;

    printf("Keine Angst - das Programm wartet nur 10 Sekunden!\n);

    tstart = time(NULL);
    start = localtime(&tstart);

    sleep(10); // Windows sollten hier Sleep verwenden und windows.h inkludieren

    tend = time(NULL);
    end = localtime(&tend);

    printf("Das Programm ist um %02d:%02d:%02d gestartet,\n,
            start->tm_hour, start->tm_min, start->tm_sec);

    printf("jetzt sind es schon %02d:%02d:%02d.\n,
            end->tm_hour, end->tm_min, end->tm_sec);
}

Wer überrascht ist, daß die Ausgabe wie folgt lautet, sollte besser weiterlesen:

Keine Angst - das Programm wartet nur 10 Sekunden!
Das Programm ist um 22:04:49 gestartet,
jetzt sind es schon 22:04:49.

Wer das Programm näher untersucht, zB mit dem Debugger, wird schnell feststellen, daß die verwendeten Routinen time und localtime korrekt arbeiten, insbesondere ist lend wirklich um 10 größer als lstart. Etwas genauer muß man ggf. hinsehen, um zu erkennen daß end und start ab Zeile 20 garantiert identisch sind. Das heißt: localtime gibt stets den gleichen Zeiger zurück.

localtime gibt immer den gleichen Zeiger zurück, was insofern schon mal beruhigend ist, weil das oben vorgestellte Listing tatsächlich ansonsten korrekt ist und kein Memoryleak hat (denn wenn localtime verschiedene Zeiger zurückgeben würde, so wäre die nächste Frage gewiß die, wer denn den Speicher freigeben würde, auf den dann diese Zeiger zu zeigen pflegen). Offenkundig arbeitet localtime mit einem statischen Buffer. Dies tun übrigens auch andere Funktionen der Standardlibrary - und das ist auch kein Geheimnis, sondern steht meist recht fett in der Dokumentation.

2.2. Lösung

Die Lösung ist damit mehr oder weniger vorgegeben: man speichert die interessanten Felder in geeignete Variablen, so daß ein erneuter Aufruf von localtime nicht dazu führt, daß das alte Datum überschrieben wird. Schade nur, daß diese Lösung erstens ein wenig umständlich und zum anderen nicht threadsicher ist: verwenden nämlich zwei Threads localtime, so kann es sein, daß einer der Threads das Ergebnis des anderen überschreibt, bevor er die interessanten Felder zwischenspeichern konnte.

[p]Letzteres Problem bekommt man dadurch in den Griff, indem man localtime_r (eigentlich ein BSD Funktion) aufruft. Das "_r" bedeutet "reentrant", was soviel heißt wie "threadsicher". Im Unterschied zu localtime verwendet localtime_r keinen statischen Puffer, man muß einen Zeiger auf den Bereich mitliefern, wo das struct tm abgelegt werden soll. Zur Demonstration die veränderte, korrekte Variante:[p]









10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
#include <stdio.h>
#include <time.h>
#include <unistd.h>

int main() {
    struct tm start;
    struct tm end;
    time_t tstart;
    time_t tend;

    printf("Keine Angst - das Programm wartet nur 10 Sekunden!\n);

    tstart = time(NULL);
    localtime_r(&tstart, &start);

    sleep(10); // Windows sollten hier Sleep verwenden und windows.h inkludieren

    tend = time(NULL);
    localtime_r(&tend, &end);

    printf("Das Programm ist um %02d:%02d:%02d gestartet,\n
           "jetzt sind es schon %02d:%02d:%02d.\n,
            start.tm_hour, start.tm_min, start.tm_sec,
            end.tm_hour, end.tm_min, end.tm_sec);
}

Die nun korrekt Ausgabe würde lauten:

Keine Angst - das Programm wartet nur 10 Sekunden!
Das Programm ist um 22:04:39 gestartet,
jetzt sind es schon 22:04:49.


Ihre Meinung ist mir wichtig

Haben Sie einen Fehler gefunden, wollen etwas anmerken oder fragen? Gefällt Ihnen etwas nicht oder wollen Sie ein Lob loswerden? - Nur raus damit!