Speicherkontrolle

Martin Kompf

C und C++ überlassen die Verwaltung von dynamischem Speicher dem Programmierer. Macht dieser dabei Fehler, beginnt eine meist aufwendige Suchaktion. Memory-Debugger helfen dabei.

Die Verwendung eines solchen Tools sei beispielhaft am relativ einfach handzuhabenden MemWatch erklärt.

Ein Entwickler erstellt eine Funktion concat zum Verketten von Strings und testet diese mittels eines einfachen Programmes:

#include <stdio.h>
#include <string.h>
#include <time.h>


char *concat( const char *a, const char *b)
{
    char *r;
    r = (char *)malloc(strlen(a) + strlen(b));
    strcpy( r, a);
    strcat( r, b);
    return r;
}


int main( int argc, char **argv)
{
    char *s;

    time_t ltime;
    time( &ltime);
    s = concat("Die Uhrzeit ist ", ctime(&ltime));
    printf( "%s\n", s);

    return 0;
}

Das Programm läßt sich problemlos übersetzen und ausführen. Die Funktion concat wird daher als korrekt befunden und in einer Serveranwendung eingesetzt. Hier treten nach einiger Zeit jedoch seltsame Effekte auf, die sich im Vorhandenensein seltsamer Zeichen in Ausgabedaten äußert. Außerdem fällt auf, daß das Serverprogramm immer mehr Speicher verbraucht, so daß der Entwickler sich entscheidet, seinen Code mit dem Memory-Debugger MemWatch zu testen.

Dies erfolgt wieder anhand des kleinen Testprogrammes. Die MemWatch-Distribution enthält die Dateien memwatch.h und memwatch.c. Erstere wird per

#include "memwatch.h"

in das Testprogramm eingebunden, memwatch.c muß zusätzlich kompiliert und zum Projekt hinzugelinkt werden. Während des Kompilierens aller Module muß der Compilerschalter -D MEMWATCH gesetzt sein.

Läßt man nun das Programm erneut ablaufen, so wird jetzt die Datei memwatch.log erzeugt. Ein Blick in diese zeigt uns die Meldung:

unfreed: <1> l:\project\tconcat.c(10), 41 bytes at 00134DF2 [overflowed]
{44 69 65 20 55 68 72 7A 65 69 74 20 69 73 74 20 Die Uhrzeit ist }

Sie weist darauf hin, daß der in Zeile 10 allokierte Bereich nicht freigegeben wird. Und richtig, eine genauere Betrachtung des Programmes zeigt, daß dies wirklich der Fall ist! Ein wiederholter Aufruf von concat in einem Serverprogramm wird zu dem beobachteten Speicherleck führen. Wir korrigieren dies durch Einfügen der Zeile

    free(s);

nach dem printf und lassen das Programm erneut ablaufen. MemWatch weist uns nun auf einen weiteren Fehler hin:

overflow: <2> l:\project\tconcat.c(25), 41 bytes alloc'd at <1>
l:\mds\memwatch\memwatch-2.64\tmw.c(10)

Es wurde über den allokierten Bereich hinausgeschrieben (overflow)! Eine nochmalige Betrachtung des Programms führt uns zum Übeltäter. In Zeile 10 wird beim malloc nicht berücksichtigt, daß die terminierende Null am Stringende ein zusätzliches Byte beansprucht. Nach Änderung dieser Zeile in

    r = (char *)malloc(strlen(a) + strlen(b) +1);

hat dann MemWatch auch nichts mehr auszusetzen. Die Implementierung dieser Änderungen in das Serverprogramm führt zu einer stabilen Version, die beschriebenen Fehler sind behoben.

Der Einsatz von MemWatch zum Aufspüren von Speicherallokierungsfehlern in C-Projekten gestaltet sich problemlos, wenn beachtet wird:

  1. Jedes Modul (.c Datei) muß memwatch.h inkludieren, um sinnvolle Ergebnisse zu erzielen.
  2. Beim Übersetzen jedes Modules muß der Compilerschalter -D MEMWATCH gesetzt sein.
  3. MemWatch verlangsamt natürlich die Programmausführung. Man kann jedoch nach Abschluß der Tests beim Erzeugen der Produktionsversion einfach wieder den Compilerschalter -D MEMWATCH entfernen. Das damit erzeugt Executable unterscheidet sich nicht von einer normalen Version ohne MemWatch-Support.

MemWatch wurde so wie hier beschrieben unter Windows mit den Compilern LCC-Win32, Borland C++ 5.5 und Cygwin GNU getestet.