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 denvoid
-Zeiger nicht,malloc()
gab damals einenchar *
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, dassmalloc()
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 vonshort
zuint
ändern, aber vergessen dassizeof
beim Aufruf vonmalloc()
in Zeile 7 auch zu ändern, dann würde (je nach System) u.U. zu wenig Speicher für einint
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, davoid *
nicht dereferenziert werden kann.3. Zu jedem malloc() ein free()
Da der Aufruf von
malloc()
(ebensocalloc()
und evtl. auchrealloc()
[*]) 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 mitfree()
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 vonrealloc()
gleichNULL
, dann ist der Aufruf äquivalent zu einem Aufruf vonmalloc()
.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 WertNULL
zurückliefern. Dabei würde die inptr
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 vonrealloc()
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