[FAQ] Dynamischer Speicher mit malloc/calloc/realloc/free
-
Was haltet ihr von folgendem, geplanten FAQ-Eintrag zum Thema?
<- snip ->
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<- snap ->
Bei den TODO: Tags könntet ihr mir helfen, ansonsten natürlich Verbesserungsvorschläge posten etc. Aber das Ding soll kein Roman werden
Gruß und guten Rutsch
-
Tim schrieb:
Tags könntet ihr mir helfen, ansonsten natürlich Verbesserungsvorschläge posten etc. Aber das Ding soll kein Roman werden
ist eh' schon lang, ein paar links hätten auch gereicht, oder du machst 'nen beitrag für's cpp.de-magazin daraus.
ansonsten: der quelltext von einer echten malloc/free/realloc-implemantation wär' vielleicht nett, damit jeder sehen kann wie sowas geht. und vielleicht könntest du noch alternativen nennen für dynamisches speichermanagement.
-
rezensions-freak schrieb:
Tim schrieb:
Tags könntet ihr mir helfen, ansonsten natürlich Verbesserungsvorschläge posten etc. Aber das Ding soll kein Roman werden
ist eh' schon lang, ein paar links hätten auch gereicht, oder du machst 'nen beitrag für's cpp.de-magazin daraus.
Nee, das will ich eigentlich nicht.
rezensions-freak schrieb:
ansonsten: der quelltext von einer echten malloc/free/realloc-implemantation wär' vielleicht nett, damit jeder sehen kann wie sowas geht. und vielleicht könntest du noch alternativen nennen für dynamisches speichermanagement.
Ersteres wäre imho nicht passend für einen FAQ-Eintrag bez. malloc und Konnsorten. Mit "Alternativen" meinst sowas wie böhm-gc? Könnte man erwähnen, ja.
-
Tim schrieb:
Mit "Alternativen" meinst sowas wie böhm-gc? Könnte man erwähnen, ja.
ne, eher sowas wie 'Simple Segregated Storage' --> http://www.boost.org/libs/pool/doc/concepts.html
-
O.K. aber das würde imho zu weit führen.
-
Tim schrieb:
O.K. aber das würde imho zu weit führen.
stimmt, dein text behandelt ja speziell malloc/realloc/free usw.
na gut, was mir noch einfällt:
- was passiert wenn man malloc(0), free(0) usw. aufruft?
- wie reagieren wenn malloc 0 zurückgibt?
- probleme mit malloc/realloc und IPC, shared libraries, dlls (verschiedene heaps!)?
- werdem memory leaks hinterlassen, wenn ein programm abschmiert.
- vorteile/nachteil von malloc/free/realloc in systemen mit wenig speicher.
- wo bekommen malloc/realloc den speicher her?
-
heap-fan schrieb:
Tim schrieb:
O.K. aber das würde imho zu weit führen.
stimmt, dein text behandelt ja speziell malloc/realloc/free usw.
na gut, was mir noch einfällt:
- was passiert wenn man malloc(0), free(0) usw. aufruft?
- wie reagieren wenn malloc 0 zurückgibt?
- probleme mit malloc/realloc und IPC, shared libraries, dlls (verschiedene heaps!)?
- werdem memory leaks hinterlassen, wenn ein programm abschmiert.
- vorteile/nachteil von malloc/free/realloc in systemen mit wenig speicher.
- wo bekommen malloc/realloc den speicher her?
gut, dann fang schonmal an
-
heap-fan schrieb:
Tim schrieb:
O.K. aber das würde imho zu weit führen.
stimmt, dein text behandelt ja speziell malloc/realloc/free usw.
na gut, was mir noch einfällt:
- was passiert wenn man malloc(0), free(0) usw. aufruft?
- wie reagieren wenn malloc 0 zurückgibt?
- probleme mit malloc/realloc und IPC, shared libraries, dlls (verschiedene heaps!)?
- werdem memory leaks hinterlassen, wenn ein programm abschmiert.
- vorteile/nachteil von malloc/free/realloc in systemen mit wenig speicher.
- wo bekommen malloc/realloc den speicher her?
Sorry, aber das ist mir schon zu speziell (Plattformspezifisch etc.) als dass ich das in einen FAQ-Eintrag hier in ANSI C behandeln würde. Das wären imho alles Themen für einen Artikel.
Und bez. "wie reagieren wenn malloc 0 zurückgibt?": Da gibts doch eh kein 100%-Gelingen-Rezept. Da gabs in "Rund um die Programmierung" erst letztens eine Diskussion, ob man dem Fall überhaupt noch die Chance hat was zu retten etc. Nein, das würde den Eintrag sprengen. Ich will hier kein "C von A bis F" schreiben.
-
jepp schrieb:
gut, dann fang schonmal an
okay, -->
X. malloc(0)
Ein Aufruf von malloc() mit 0 als Argument ist implementationsabhängig. Es sind zwei Resultate möglich.
1. malloc() gibt einen NULL Zeiger zurück.
2. malloc() gibt einen Zeiger ungleich NULL zurück. Obwohl kein benutzbarer Speicher alloziert wurde, existiert auf dem Heap ein Listeneintrag, der mit free() wieder gelöscht werden muß.
-
gut,
man könnte sich die frage stellen, welcher grund einen programmierer bewegen könnte 0 byte arbeitsspeicher anzufordern *grübel*
-
jepp schrieb:
man könnte sich die frage stellen, welcher grund einen programmierer bewegen könnte 0 byte arbeitsspeicher anzufordern *grübel*
das könnte z.b. bei berechneten mallocs passieren, etwa bei malloc (a * sizeof(b));, wenn b 0 ist.
-
Gut, aber das ist dann ein Bug den man fixt ohne wissen zu müssen wie malloc() in so einem Fall reagiert (was ja eh wieder "implementation defined" wäre).
edit: außerdem meinst du wohl eher "wenn a 0 ist"
-
Tim schrieb:
edit: außerdem meinst du wohl eher "wenn a 0 ist"
ja, das meinte ich.
-
Ich fände es gut, wenn du die richtige Verwendung von man: realloc(3) erklären könntest. Man sieht zu oft
p = realloc(p, N)
.
-
rüdiger schrieb:
Ich fände es gut, wenn du die richtige Verwendung von man: realloc(3) erklären könntest. Man sieht zu oft
p = realloc(p, N)
.Das habe ich mir hier, etwas kryptisch vielleicht, notiert:
TODO: Mögliches Speicherleck bei falschem Aufruf von realloc()
-
btw. zum casten von malloc. Auf m68k kann das wohl böse enden, da Adressen und Integer dort unterschiedliche Register haben: http://www.grep.be/blog/en/computer/debian/m68k/broken_c_code
-
rüdiger schrieb:
btw. zum casten von malloc. Auf m68k kann das wohl böse enden, da Adressen und Integer dort unterschiedliche Register haben
das war wohl eher ein unfähiger compiler, der casts nicht richtig beherrscht.
-
nö, weil in dem Beispiel in der Datei mit der main-Funktion die Funktion als void *f(); deklariert wurde. Aber das ist auch total OT...
-
rüdiger schrieb:
nö, weil in dem Beispiel in der Datei mit der main-Funktion die Funktion als void *f(); deklariert wurde.
stimmt, du hast recht. falscher prototyp, das hab' ich übersehen.