Dynamischer Speicher mit malloc/calloc/realloc/free



  • 0. Read the fine manual

    Als Begleitlektüre: man: malloc(3), man: calloc(3), man: realloc(3), man: free(3)

    1. malloc() nicht casten

    Man sieht es immer wieder, malloc() (bzw. der Rückgabewert) wird explizit in den Typen der empfangenden Variable gecastet. Das ist in C absolut unnötig, wird als schlechter Stil angesehen und kann zu Problemen führen.

    1.1 Warum ist es unnötig?

    Die Sprache C kennt seit ihrer ANSI-Standardisierung im Jahre 1989 den allseits bekannten void -Zeiger. Dieser hat die Eigenschaft, dass er implizit in Zeiger anderen Typs umgewandelt wird. Eine explizite Umwandlung ist damit unnötig.

    Ausnahmen:
    - der Code soll (aus welchen Gründen auch immer) kompatibel zu C++ sein.
    - vor der ANSI-Standardisierung gab es den void -Zeiger nicht, malloc() gab damals einen char * zurück, welcher dann gecastet werden musste. Dies sollte aber wirklich nur noch bei der Wartung von sehr altem Code ein Thema sein.

    1.2 Zu welchen Problemen kann es kommen?

    Sollte man vergessen haben, die Header-Datei stdlib.h einzubinden, könnte der Compiler annehmen, dass malloc() ein Integer zurückgibt, was natürlich falsch ist. Ohne Typecast wird der Compiler den Code aller Wahrscheinlichkeit nach mit einer Warnung versehen. Mit Typecast aber würde eben diese Warnung verloren gehen, und der Fehler des vergessenen Headers wäre hier nicht mehr (durch Warnungen) zu erkennen.

    Hinweis:
    - aktuelle Compiler würden auch mit Typecast eine Warnung ausgeben. z.B. der gcc: "warning: implicit declaration of function ‘malloc’"

    2. Hartkodierte Typen im Aufruf von malloc() vermeiden

    Betrachten wird einen typischen Aufruf von malloc() :

    short *ptr;
    
    // andere Deklarationen
    
    // anderer Code
    
    ptr = malloc ( sizeof(short) );
    
    free ( ptr );
    

    Zwar ist dieser Code nicht falsch, er enthält aber einen kleinen Fallstrick:
    Würde der Programmierer die Deklaration von ptr in Zeile 1 zu einem späteren Zeitpunkt von short zu int ändern, aber vergessen das sizeof beim Aufruf von malloc() in Zeile 7 auch zu ändern, dann würde (je nach System) u.U. zu wenig Speicher für ein int reserviert werden und der Code (mit etwas Glück) crashen.

    Dieses Problem kann vermieden werden, indem man den sizeof -Operator nicht auf einen Typ, sondern auf ein konkretes Objekt anwendet:

    short *ptr;
    
    // andere Deklarationen
    
    // anderer Code
    
    ptr = malloc ( sizeof(*ptr) );
    
    free ( ptr );
    

    Hier sollte klar sein, dass eine Änderung des Typs in Zeile 1 auch automatisch in Zeile 7 einfliesst. Wichtig ist hier zu erkennen, dass die Zeigervariable ptr bei sizeof immer dereferenziert werden muss.

    Ausnahme:
    - Soll Speicher für einen void-Zeiger reserviert werden, geht diese Variante nicht, da void * nicht dereferenziert werden kann.

    3. Zu jedem malloc() ein free()

    Da der Aufruf von malloc() (ebenso calloc() und evtl. auch realloc() [*]) Speicher reserviert, welcher auch über den Scope des Aufrufs erhalten bleibt, ist es notwendig diesen Speicher irgendwann (meistens gleich dann wenn er nicht mehr benötigt wird) wieder mit free() freizugeben. Wird dies nicht getan, entstehen Speicherlecks, welche je nach Programm unkritisch bis fatal sein können. In jedem Fall ist ein Speicherleck aber unschön und sollte vermieden werden.

    [*] Normalerweise wird mit realloc() ein bereits reservierter Speicherbereich vergrößert oder verkleinert. Ist allerdings das erste Argument von realloc() gleich NULL , dann ist der Aufruf äquivalent zu einem Aufruf von malloc() .

    3.1 Loggen von Reservierungen/Freigaben

    Eine einfache aber bereits wirksame Methode zum Überprüfen von Reservierungen/Freigaben ist es, die jeweiligen Funktionen zu wrappen und ihre Aufrufe einfach mitzuzählen. Die Implementierung könnte wie folgt aussehen:

    /* In der Datei wrapper.h würden natürlich die Prototypen der
    Funktionen stehen und stdlib.h sowie stdio.h eingebunden */
    #include "wrapper.h"
    
    /* Zaehlervariablen */
    static int num_alloc;
    static int num_free;
    
    void *wrapper_malloc ( size_t size ) {
    	void *tmp = malloc ( size );
    	if ( tmp != NULL ) {
    		num_alloc++;
    	}
    	return tmp;
    }
    
    void *wrapper_calloc ( size_t nmemb, size_t size ) {
    	void *tmp = calloc ( nmemb, size );
    	if ( tmp != NULL ) {
    		num_alloc++;
    	}
    	return tmp;
    }
    
    void *wrapper_realloc ( void *ptr, size_t size ) {
    	void *tmp = realloc ( ptr, size );
    	// wenn ptr gleich NULL, dann aequivalent zu malloc() behandeln
    	if ( tmp != NULL && ptr == NULL ) {
    		num_alloc++;
    	}
    	return tmp;
    }
    
    void  wrapper_free ( void *ptr ) {
    	free ( ptr );
    	num_free++;
    }
    
    void  wrapper_stats ( void ) {
    	printf ( "Number of   allocations   : %10d\n", num_alloc );
    	printf ( "Number of deallocations   : %10d\n", num_free  );
    	printf ( "Therefore lost allocations: %10d\n", num_alloc - num_free );
    }
    

    3.2 realloc() richtig angewendet

    Bei der Verwendung von realloc() zum vergrößern/verkleinern eines bereits reservierten Speicherbereiches übergibt man einen Zeiger auf eben diesen Speicherbereich.

    // für ptr wurde bereits Speicher reserviert
    ptr = realloc ( ptr, 10 * sizeof(*ptr) );
    

    Das Problem bei diesem Code: Es könnte (warum auch immer) passieren, dass eine Vergößerung nicht möglich ist. In diesem Fall würde realloc() den Wert NULL zurückliefern. Dabei würde die in ptr abgelegte Adresse allerdings verlorengehen und damit auch die wahrscheinlich einzige Möglichkeit auf den Speicher dahinter zuzugreifen. Besser ist es, ptr erst dann die neue Adresse zuzuweisen, nachdem man das Ergebnis von realloc() getestet hat:

    // für ptr wurde bereits Speicher reserviert
    void *temp;
    temp = realloc ( ptr, 10 * sizeof(*ptr) );
    if ( temp != NULL ) {
        ptr = temp;
    }
    

    3.3 Tools zum prüfen auf Speicherlecks

    Eine (noch) kleine Sammlung an Tools/Libs zum aufspüren von Speicherlecks:
    - Valgrind
    - DUMA


Anmelden zum Antworten