Speicher freigeben mit free() aus Funktion heraus.
-
Hallo,
ich habe ein Verständnisproblem mit der Funktion free(). Kann ich den Speicherbereich den ich mit malloc() reserviert habe in einer Funktion wieder freigeben? free() hat ja leider keinen Rückgabewert mit dem ich kontrollieren könnte ob die Freigabe funktioniert hat.
#include <stdlib.h> #include <stdio.h> void testfree(int *pnt) { free(pnt); } int main(void) { int *ipnt; ipnt=malloc(sizeof(ipnt)); if(ipnt==NULL) { printf("\n Fehler bei der Speicherreservierung!"); exit(1); } testfree(ipnt); return(0); }
-
Du kannst free aufrufen wo immer du willst, also ist das okay. free braucht keinen Rückgabewert, bei den gängigen Implementierung bekommst du im Debug-Modus einen schönen Programmabbruch + Fehlermeldung verpasst bzw im Releasemode zerrupfst du den Heap und es knallt sofort oder später.
Dein Code ist aber nicht ganz korrekt, du allozierst sizeof(int*) Bytes, die du einem Zeiger auf int zuweist. Auf 64bit Systemenm ist sizeof(int*) == 8, sizeof(int) aber 4.
-
Es liegt in deiner Verantwortung als Programmierer die Speicherreservierung und -befreiung im Auge zu behalten. Wenn du dynamisch Speicher reservierst, musst du auch verfolgen, wo der Speicher wieder befreit wird bzw. werden soll.
Die *alloc Funktionen geben dir die Adresse zum einem reservierten Speicherbereich zurück und free erwartet als Parameter jene Adresse um den Speicher wieder zu befreien. Befreist du Speicher aus irgendwelchen Gründen bereits früher, sollte dort auch der Wert des Pointers auf NULL / 0 gesetzt werden - kein Speicherbereich hat Adresse 0, daher ist dies ein sicherer Hinweis (und ohnehin ist free mit Parameter 0 sicher).
-
Youka schrieb:
Die *alloc Funktionen geben dir die Adresse zum einem reservierten Speicherbereich zurück und free erwartet als Parameter jene Adresse um den Speicher wieder zu befreien. Befreist du Speicher aus irgendwelchen Gründen bereits früher, sollte dort auch der Wert des Pointers auf NULL / 0 gesetzt werden - kein Speicherbereich hat Adresse 0, daher ist dies ein sicherer Hinweis (und ohnehin ist free mit Parameter 0 sicher).
Das halte ich für einen ganz schlechten Tipp. Die Null bemerkst du nämlich oft nicht (du sagst es ja selber, free mit 0 macht einfach nichts); Programmierfehler werden so vertuscht. Einen ungültigen Zeiger bemerkt man hingegen oft sofort, da es abstürzt.
Das Nullsetzen machst du nur dann, wenn es für die Programmlogik von Bedeutung ist. Zum Beispiel um das Ende von Listen zu markieren oder die Blätter von Bäumen.
-
Mit auf 0 setzen hebelt man auch effektiv Tools wie valgrind aus bzw die Debugfunktionalität der Standardbibliothek-Implementierung.
Wenn ich Folgendes mit dem GCC unter Linux kompiliere
#include <stdlib.h> int main() { void* ptr = malloc(123); free(ptr); free(ptr); }
erhalte ich dieses Ergebnis. Super um dem Fehler der Benutzung von bereits freigegebenem Speicher auf die Spur zu kommen.
[ferler@notebook-fedora17 ~]$ ./free *** glibc detected *** ./free: double free or corruption (top): 0x0000000001ec8010 *** ======= Backtrace: ========= /lib64/libc.so.6[0x34c847c00e] ./free[0x40055a] /lib64/libc.so.6(__libc_start_main+0xf5)[0x34c8421735] ./free[0x400449] ======= Memory map: ======== 00400000-00401000 r-xp 00000000 fd:00 262386 /home/ferler/free 00600000-00601000 rw-p 00000000 fd:00 262386 /home/ferler/free 01ec8000-01ee9000 rw-p 00000000 00:00 0 [heap] 34c8000000-34c8020000 r-xp 00000000 fd:00 160615 /usr/lib64/ld-2.15.so 34c821f000-34c8220000 r--p 0001f000 fd:00 160615 /usr/lib64/ld-2.15.so 34c8220000-34c8221000 rw-p 00020000 fd:00 160615 /usr/lib64/ld-2.15.so 34c8221000-34c8222000 rw-p 00000000 00:00 0 34c8400000-34c85ac000 r-xp 00000000 fd:00 161433 /usr/lib64/libc-2.15.so 34c85ac000-34c87ac000 ---p 001ac000 fd:00 161433 /usr/lib64/libc-2.15.so 34c87ac000-34c87b0000 r--p 001ac000 fd:00 161433 /usr/lib64/libc-2.15.so 34c87b0000-34c87b2000 rw-p 001b0000 fd:00 161433 /usr/lib64/libc-2.15.so 34c87b2000-34c87b7000 rw-p 00000000 00:00 0 34cbc00000-34cbc15000 r-xp 00000000 fd:00 171701 /usr/lib64/libgcc_s-4.7.2-20120921.so.1 34cbc15000-34cbe14000 ---p 00015000 fd:00 171701 /usr/lib64/libgcc_s-4.7.2-20120921.so.1 34cbe14000-34cbe15000 rw-p 00014000 fd:00 171701 /usr/lib64/libgcc_s-4.7.2-20120921.so.1 7ff18c6a5000-7ff18c6a8000 rw-p 00000000 00:00 0 7ff18c6cd000-7ff18c6cf000 rw-p 00000000 00:00 0 7fff88839000-7fff8885a000 rw-p 00000000 00:00 0 [stack] 7fff889c6000-7fff889c8000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] Abgebrochen (Speicherabzug geschrieben)
Deutlich besser als den Fehler zu schlucken, auch wenn es dann nicht abstürzt ... dafür hätte man irgendwann mit sehr schwer auffindbarem undefiniertem Verhalten zu kämpfen.
-
Dass auf
NULL
setzen ist IMHO knicht schlecht, höchstens unnötig.
Es wird zum Problem wenn man es macht, um danach wahllosfree()
auf seinen Pointern aufzurufen. "Da wird ja entweder Speicher dahinterliegen, oder der Pointer ist NULL".Von dem Argument, dass double frees nicht mehr bemerkt werden, oder valgrind nicht mehr so will, wie es sollte bin ich nicht überzeugt. Mein Stil soll sich ja nicht nach solchen Helferlein richten. Ausserdem kann ich auch andersrum argumentieren, dass z.B. ein
memset()
auf einenNULL
Zeiger, oder allgemein deren Dereferenzierung schneller auffällt als bei einem hängenden Zeiger.
-
Furble Wurble schrieb:
Mein Stil soll sich ja nicht nach solchen Helferlein richten.
Das sehe ich anders. Wartbarkeit im Auge zu haben, ist eine Tugend.
Gleichwohl: Es gibt Fälle, in denen das Nullen von Zeigern nach dem free bzw. die mögliche Übergabe von Nullzeigern an free auch außerhalb verketteter Datenstrukturen sinnvoll ist. Ganz einfaches Beispiel anhand GNUs getline-Funktion:
void process_file(FILE *fd) { char *line = NULL; size_t len = NULL; while(getline(&line, &n, fd) { do_something(line); } free(line); /* Wenn am Anfang der Funktion feof(fd) ist, ist line hier NULL */ }
Oder zur Vereinfachung der Aufräumarbeiten:
char *data1 = NULL; char *data2 = NULL; char *data3 = NULL; /* Jetzt kann ich lustig Kram machen und muss nur darauf achten, dass * free(foo); für alle Zeiger gültig bleibt. */ data1 = strdup("foo"); if(rand() % 2 == 1) { free(data1); data1 = NULL; } else { data2 = malloc(123); data3 = calloc(123, 4); } /* ...dann muss ich mir hier keine Gedanken darum machen, was wie wann * wegkommt. */ free(data1); free(data2); free(data3);
Grad letzteres kann oft die wilden goto-Konstrukte ersetzen, die in manchen Kreisen zur Fehlerbehandlung populär sind.
-
Furble Wurble schrieb:
Dass auf
NULL
setzen ist IMHO knicht schlecht, höchstens unnötig.
Es wird zum Problem wenn man es macht, um danach wahllosfree()
auf seinen Pointern aufzurufen. "Da wird ja entweder Speicher dahinterliegen, oder der Pointer ist NULL".Von dem Argument, dass double frees nicht mehr bemerkt werden, oder valgrind nicht mehr so will, wie es sollte bin ich nicht überzeugt. Mein Stil soll sich ja nicht nach solchen Helferlein richten.
Wo ist denn der Vorteil des Nullsetzens? Du musst den größeren Aufwand rechtfertigen, nicht umgekehrt.
Ausserdem kann ich auch andersrum argumentieren, dass z.B. ein
memset()
auf einenNULL
Zeiger, oder allgemein deren Dereferenzierung schneller auffällt als bei einem hängenden Zeiger.Das heißt beim Nullen stürzt es in manchen Fällen ebenfalls ab, aber nicht in allen. Beim Zugriff auf freigegebene Speicherbereiche darfst du hingegen so gut wie sicher mit einem Absturz rechnen. Klingt nicht nach einem guten Tausch.
-
SeppJ schrieb:
Furble Wurble schrieb:
Dass auf
NULL
setzen ist IMHO knicht schlecht, höchstens unnötig.
Es wird zum Problem wenn man es macht, um danach wahllosfree()
auf seinen Pointern aufzurufen. "Da wird ja entweder Speicher dahinterliegen, oder der Pointer ist NULL".Von dem Argument, dass double frees nicht mehr bemerkt werden, oder valgrind nicht mehr so will, wie es sollte bin ich nicht überzeugt. Mein Stil soll sich ja nicht nach solchen Helferlein richten.
Wo ist denn der Vorteil des Nullsetzens? Du musst den größeren Aufwand rechtfertigen, nicht umgekehrt.
Mal abgesehen davon, dass ich das hier niemand etwas rechtfertigen muss kann es durchaus guter defensiver Stil sein, hängende Zeiger zu vermeiden.
SeppJ schrieb:
Ausserdem kann ich auch andersrum argumentieren, dass z.B. ein
memset()
auf einenNULL
Zeiger, oder allgemein deren Dereferenzierung schneller auffällt als bei einem hängenden Zeiger.Das heißt beim Nullen stürzt es in manchen Fällen ebenfalls ab, aber nicht in allen. Beim Zugriff auf freigegebene Speicherbereiche darfst du hingegen so gut wie sicher mit einem Absturz rechnen.
Gibt es auch eine Begründung für Deine Behauptung? Die werde ich gerne zur Kenntnis nehmen, aber ich denke Du solltest den Thread nicht kapern mit Deiner Empfehlung.
-
Furble Wurble schrieb:
SeppJ schrieb:
Wo ist denn der Vorteil des Nullsetzens? Du musst den größeren Aufwand rechtfertigen, nicht umgekehrt.
Mal abgesehen davon, dass ich das hier niemand etwas rechtfertigen muss
Doch. Wenn du propagierst, dass man etwas machen sollte, im Gegensatz zum Nichtstun, dann muss es einen Grund dafür geben
kann es durchaus guter defensiver Stil sein, hängende Zeiger zu vermeiden.
Ok, das ist doch ein Grund.
SeppJ schrieb:
Ausserdem kann ich auch andersrum argumentieren, dass z.B. ein
memset()
auf einenNULL
Zeiger, oder allgemein deren Dereferenzierung schneller auffällt als bei einem hängenden Zeiger.Das heißt beim Nullen stürzt es in manchen Fällen ebenfalls ab, aber nicht in allen. Beim Zugriff auf freigegebene Speicherbereiche darfst du hingegen so gut wie sicher mit einem Absturz rechnen.
Gibt es auch eine Begründung für Deine Behauptung?
Klar gibt es eine Begründung für diese "Behauptung". Die Begründung ist, dass es eine objektive, technische Tatsache ist, keine Behauptung. Es gibt oft vorkommende Situationen, in denen durch die Sonderbehandlung des Nullzeigers das Entdecken von Programmfehlern verhindert wird (zum Beispiel bei free, aber auch bei den weit verbreiteten
if (ptr) *ptr...
, die oft von den Nullsetzern benutzt werden), hingegen führen (fast) alle Situationen in denen ein Nullzeiger zum Absturz führt auch zum Absturz mit einem ungültigen Pointer oder zum Anschlagen des Speicherdebuggers (außer man konstruiert mit Absicht einen Fall, in dem das nicht so ist).
-
SeppJ schrieb:
Es gibt oft vorkommende Situationen, in denen durch die Sonderbehandlung des Nullzeigers das Entdecken von Programmfehlern verhindert wird (zum Beispiel bei free, aber auch bei den weit verbreiteten
if (ptr) *ptr...
, die oft von den Nullsetzern benutzt werden), hingegen führen (fast) alle Situationen in denen ein Nullzeiger zum Absturz führt auch zum Absturz mit einem ungültigen Pointer oder zum Anschlagen des Speicherdebuggers (außer man konstruiert mit Absicht einen Fall, in dem das nicht so ist).Du betrachtest IMHO nur die dunkle Seite der NULL: Fehler unter den Tisch fallen lassen (
if (p!=NULL) ...
).
Das, was die "Nullsetzer" machen lässt sich aber ohne Aufwand zum Fehlerfinden mit Sprachmitteln benutzen (assert(p!=NULL)
).Wie gesagt: ich propagiere nicht den gedankenlosen Einsatz dieses Idioms um alles und jeden auch mit einem NULL pointer aufrufen zu koennen (Was irgendwann schiefgehen wird.).
Ich propagiere, dass es hilft Fehler zu vermeiden und Fehler zu finden - und nicht in Bausch und Bogen zu verdammen ist.
-
Furble Wurble schrieb:
Wie gesagt: ich propagiere nicht den gedankenlosen Einsatz dieses Idioms um alles und jeden auch mit einem NULL pointer aufrufen zu koennen (Was irgendwann schiefgehen wird.).
So formuliert stimme ich dir natürlich zu. Ich fürchte bloß, die meisten Einsätze sind jedoch gerade dies: Gedankenlos.
(Vielleicht habe ich auch einfach eine zu schlechte Meinung vom durchschnittlichen Nullsetzer, weil wir hier im Forum nur die schlechten Codes sehen)
-
Furble Wurble schrieb:
Ich propagiere, dass es hilft Fehler zu vermeiden und Fehler zu finden - und nicht in Bausch und Bogen zu verdammen ist.
Jeder Code der zur Fehlerverdeckung führt, sei es zur Compile- oder Laufzeit, ist Schrott. Das propagiere ich.
Irgendwelche Pseudoausnahmen bringen nur wieder zusätzliche und vor allem unnötige Abhängigkeiten in deinen Code und sowas schadet immer der Les/Wartbarkeit.