Was passiert WIRKLICH, wenn Sie nach malloc nicht frei sind?

538

Das hat mich schon seit Ewigkeiten gestört.

In der Schule wird uns allen beigebracht (zumindest war ich das), dass Sie jeden zugewiesenen Zeiger freigeben MÜSSEN. Ich bin allerdings etwas neugierig auf die tatsächlichen Kosten, die entstehen, wenn kein Speicher freigegeben wird. In einigen offensichtlichen Fällen, z. B. wenn mallocsie innerhalb einer Schleife oder eines Teils einer Thread-Ausführung aufgerufen werden, ist es sehr wichtig, sie freizugeben, damit keine Speicherlecks auftreten. Betrachten Sie jedoch die folgenden zwei Beispiele:

Erstens, wenn ich Code habe, der ungefähr so ​​aussieht:

int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}

Was ist das wirkliche Ergebnis hier? Ich denke, dass der Prozess abbricht und dann der Heap-Speicherplatz ohnehin weg ist, sodass es nicht schadet, den Aufruf zu verpassen free(ich erkenne jedoch, wie wichtig es ist, ihn trotzdem für den Abschluss, die Wartbarkeit und bewährte Verfahren zu haben). Habe ich recht mit diesem Denken?

Zweitens, nehmen wir an, ich habe ein Programm, das sich ein bisschen wie eine Shell verhält. Benutzer können Variablen wie deklarieren aaa = 123und diese werden zur späteren Verwendung in einer dynamischen Datenstruktur gespeichert. Es ist klar, dass Sie eine Lösung verwenden würden, die eine * Alloc-Funktion aufruft (Hashmap, verknüpfte Liste, so etwas). Für diese Art von Programm ist es nicht sinnvoll, nach dem Aufruf jemals freizugeben, mallocda diese Variablen während der Programmausführung jederzeit vorhanden sein müssen und es keine gute Möglichkeit gibt (wie ich sehen kann), dies mit statisch zugewiesenem Speicherplatz zu implementieren. Ist es schlechtes Design, eine Menge Speicher zu haben, der zugewiesen, aber nur als Teil des Prozessendes freigegeben wird? Wenn ja, was ist die Alternative?

Scott
quelle
7
@NTDLS Die Magie des Bewertungssystems, das tatsächlich einmal funktioniert: 6 Jahre später und die "verdientere" Antwort ist in der Tat an die Spitze gestiegen.
zxq9
15
Die folgenden Leute sagen immer wieder, dass ein gutes modernes Betriebssystem bereinigt, aber was ist, wenn der Code im Kernelmodus ausgeführt wird (z. B. aus Leistungsgründen)? Sind Kernel-Modus-Programme (z. B. unter Linux) in einer Sandbox? Wenn nicht, müssten Sie wahrscheinlich alles manuell freigeben, noch vor abnormalen Abbrüchen wie mit abort ().
Dr. Person Person II
3
@ Dr.PersonPersonII Ja, Code, der im Kernelmodus ausgeführt wird, muss normalerweise alles manuell freigeben.
zwol
1
Ich möchte hinzufügen, dass free(a)dies nicht wirklich dazu beiträgt, Speicherplatz freizugeben! Es werden lediglich einige Zeiger in der libc-Implementierung von malloc zurückgesetzt, die die verfügbaren Speicherblöcke auf einer großen zugeordneten Speicherseite (im Allgemeinen als "Heap" bezeichnet) verfolgen. Diese Seite wird immer noch nur freigegeben, wenn Ihr Programm beendet wird, nicht vorher.
Marco Bonelli
1
Free () kann den Speicher tatsächlich freigeben oder auch nicht. Es kann lediglich den Block als freigegeben markieren, um später zurückgefordert zu werden, oder ihn in eine freie Liste einbinden. Es könnte es in benachbarte freie Blöcke zusammenführen oder dies für eine nachfolgende Zuweisung belassen. Es ist alles ein Implementierungsdetail.
Jordan Brown

Antworten:

378

Nahezu jedes moderne Betriebssystem stellt nach dem Beenden eines Programms den gesamten zugewiesenen Speicherplatz wieder her. Die einzige Ausnahme, an die ich denken kann, ist möglicherweise Palm OS, bei dem der statische Speicher und der Laufzeitspeicher des Programms ziemlich identisch sind. Wenn Sie also nicht freigeben, belegt das Programm möglicherweise mehr Speicherplatz. (Ich spekuliere hier nur.)

Im Allgemeinen schadet es also nicht, außer den Laufzeitkosten für mehr Speicher als Sie benötigen. In dem von Ihnen angegebenen Beispiel möchten Sie den Speicher für eine Variable behalten, die möglicherweise verwendet wird, bis sie gelöscht wird.

Es wird jedoch als guter Stil angesehen, Speicher freizugeben, sobald Sie ihn nicht mehr benötigen, und alles freizugeben, was Sie beim Beenden des Programms noch haben. Es ist eher eine Übung, um zu wissen, welchen Speicher Sie verwenden, und darüber nachzudenken, ob Sie ihn noch benötigen. Wenn Sie nicht den Überblick behalten, können Speicherlecks auftreten.

Auf der anderen Seite hat die ähnliche Ermahnung, Ihre Dateien beim Beenden zu schließen, ein viel konkreteres Ergebnis. Wenn Sie dies nicht tun, werden die Daten, die Sie an sie geschrieben haben, möglicherweise nicht gelöscht, oder wenn es sich um eine temporäre Datei handelt, werden sie möglicherweise nicht gelöscht werden gelöscht, wenn Sie fertig sind. Außerdem sollten die Transaktionen von Datenbankhandles festgeschrieben und dann geschlossen werden, wenn Sie damit fertig sind. Wenn Sie eine objektorientierte Sprache wie C ++ oder Objective C verwenden und ein Objekt nicht freigeben, wenn Sie damit fertig sind, wird der Destruktor niemals aufgerufen, und alle Ressourcen, für die die Klasse verantwortlich ist, werden möglicherweise nicht bereinigt.

Paul Tomblin
quelle
16
Es wäre wahrscheinlich auch gut zu erwähnen, dass nicht jeder ein modernes Betriebssystem verwendet. Wenn jemand Ihr Programm nimmt (und es immer noch auf einem Betriebssystem läuft, das keinen Speicher wiederherstellt), führt es GG aus.
user105033
79
Ich halte diese Antwort wirklich für falsch. Man sollte Ressourcen immer freigeben, nachdem man damit fertig ist, sei es Dateihandles / Speicher / Mutexe. Wenn man diese Angewohnheit hat, macht man beim Erstellen von Servern keinen solchen Fehler. Einige Server werden voraussichtlich rund um die Uhr ausgeführt. In diesen Fällen bedeutet ein Leck jeglicher Art, dass Ihrem Server diese Ressource irgendwann ausgeht und er auf irgendeine Weise hängen bleibt / abstürzt. Ein kurzes Hilfsprogramm, ya ein Leck ist nicht so schlimm. Jeder Server, jedes Leck ist der Tod. Tu dir selbst einen Gefallen. Räumen Sie nach sich selbst auf. Es ist eine gute Angewohnheit.
EvilTeach
120
Welcher Teil von "Es wird jedoch als guter Stil angesehen, Speicher freizugeben, sobald Sie ihn nicht mehr benötigen, und alles freizugeben, was Sie beim Beenden des Programms noch haben." Halten Sie das dann für falsch?
Paul Tomblin
24
Wenn Sie über einen Speicher verfügen, den Sie bis zum Beenden des Programms benötigen, und Sie nicht auf einem primitiven Betriebssystem ausgeführt werden, ist die Freigabe des Speichers unmittelbar vor dem Beenden eine stilistische Entscheidung, kein Defekt.
Paul Tomblin
30
@Paul - Nur EvilTeach zuzustimmen, wird nicht als guter Stil angesehen, um Speicher freizugeben. Es ist falsch, Speicher nicht freizugeben. Ihre Formulierung lässt dies ungefähr so ​​wichtig erscheinen wie das Tragen eines Taschentuchs, das zu Ihrer Krawatte passt. Eigentlich ist es auf der Ebene des Tragens von Hosen.
Heath Hunnicutt
110

Ja, Sie haben Recht, Ihr Beispiel schadet nicht (zumindest nicht auf den meisten modernen Betriebssystemen). Der gesamte von Ihrem Prozess zugewiesene Speicher wird vom Betriebssystem wiederhergestellt, sobald der Prozess beendet wird.

Quelle: Allocation und GC Myths (PostScript-Warnung!)

Zuweisungsmythos 4: Nicht durch Müll gesammelte Programme sollten immer den gesamten von ihnen zugewiesenen Speicher freigeben.

Die Wahrheit: Ausgelassene Freigaben in häufig ausgeführtem Code führen zu wachsenden Lecks. Sie sind selten akzeptabel. Programme, die den meisten zugewiesenen Speicher bis zum Beenden des Programms behalten, weisen jedoch häufig eine bessere Leistung auf, ohne dass eine Freigabe erforderlich ist. Malloc ist viel einfacher zu implementieren, wenn es keine kostenlose gibt.

In den meisten Fällen ist es sinnlos , den Speicher unmittelbar vor dem Beenden des Programms freizugeben. Das Betriebssystem wird es trotzdem zurückfordern. Der freie Wille berührt und blättert in den toten Objekten; das Betriebssystem wird nicht.

Folge: Seien Sie vorsichtig mit "Lecksuchern", die Zuordnungen zählen. Einige "Lecks" sind gut!

Das heißt, Sie sollten wirklich versuchen, alle Speicherlecks zu vermeiden!

Zweite Frage: Ihr Design ist in Ordnung. Wenn Sie etwas speichern müssen, bis Ihre Anwendung beendet wird, können Sie dies mit dynamischer Speicherzuweisung tun. Wenn Sie die erforderliche Größe im Voraus nicht kennen, können Sie keinen statisch zugewiesenen Speicher verwenden.

compie
quelle
3
Könnte daran liegen, dass die Frage, wie ich sie lese, ist, was tatsächlich mit dem durchgesickerten Speicher passiert, und nicht, ob dieses spezielle Beispiel in Ordnung ist. Ich würde es aber nicht ablehnen, weil es immer noch eine gute Antwort ist.
DevinB
3
Wahrscheinlich gab es (frühes Windows, frühes Mac OS) Betriebssysteme, bei denen Prozesse erforderlich sind, um Speicher freizugeben, bevor sie beendet werden, andernfalls wird der Speicherplatz nicht zurückgefordert.
Pete Kirkham
Es ist vollkommen in Ordnung, es sei denn, Sie kümmern sich um Speicherfragmentierung oder es geht Ihnen der Speicher aus - Sie tun dies zu oft und die Leistung Ihrer Anwendungen verschwindet. Befolgen Sie neben den harten Fakten immer die Best Practice und den Aufbau guter Gewohnheiten.
NTDLS
1
Ich habe eine akzeptierte Antwort, die derzeit bei -11 liegt, also ist er noch nicht einmal im Rennen um den Rekord.
Paul Tomblin
8
Ich denke, es ist falsch, die Notwendigkeit zu erklären, den Speicher freizugeben (), indem man "wegen des Lecksuchers" sagt. Dies ist wie zu sagen "Sie müssen langsam in einer Spielstraße fahren, weil Polizisten mit einer Radarkamera auf Sie warten könnten".
Sebastian Mach
57

=== Was ist mit Zukunftssicherheit und Wiederverwendung von Code ? ===

Wenn Sie den Code nicht schreiben, um die Objekte freizugeben, beschränken Sie sich darauf, den Code nur dann sicher zu verwenden, wenn Sie sich darauf verlassen können, dass der Speicher durch das Schließen des Prozesses freigegeben wird ... dh eine kleine einmalige Verwendung Projekte oder "Wegwerf" -Projekte [1] ) ... wo Sie wissen, wann der Prozess endet.

Wenn Sie tun , den Code schreiben , dass freie () s alle Ihre Speicher dynamisch zugewiesen, dann verwenden Sie den Code Zukunft Proofing und andere lassen es in einem größeren Projekt.


[1] in Bezug auf "Wegwerf" -Projekte. Code, der in "Wegwerf" -Projekten verwendet wird, kann nicht weggeworfen werden. Als nächstes wissen Sie, dass zehn Jahre vergangen sind und Ihr "Wegwerf" -Code immer noch verwendet wird.

Ich habe eine Geschichte über jemanden gehört, der nur zum Spaß Code geschrieben hat, damit seine Hardware besser funktioniert. Er sagte, " nur ein Hobby, wird nicht groß und professionell sein ". Jahre später verwenden viele Leute seinen "Hobby" -Code.

Trevor Boyd Smith
quelle
8
Für "kleine Projekte" herabgestimmt. Es gibt viele große Projekte, die beim Beenden absichtlich keinen Speicher freigeben, da es Zeitverschwendung ist, wenn Sie Ihre Zielplattformen kennen. IMO, ein genaueres Beispiel wären "isolierte Projekte" gewesen. Wenn Sie beispielsweise eine wiederverwendbare Bibliothek erstellen, die in anderen Anwendungen enthalten sein wird, gibt es keinen genau definierten Austrittspunkt, sodass Sie keinen Speicher verlieren sollten. Bei einer eigenständigen Anwendung wissen Sie immer genau, wann der Prozess endet, und können sich bewusst dafür entscheiden, die Bereinigung auf das Betriebssystem zu verlagern (das die Überprüfungen in beiden Fällen durchführen muss).
Dan Bechard
Die Anwendung von gestern ist die Bibliotheksfunktion von heute und wird morgen mit einem langlebigen Server verbunden, der sie tausende Male aufruft.
Adrian McCarthy
1
@AdrianMcCarthy: Wenn eine Funktion prüft, ob ein statischer Zeiger null ist, ihn mit initialisiert malloc()und beendet, wenn der Zeiger noch null ist, kann eine solche Funktion beliebig oft sicher verwendet werden, selbst wenn sie freenie aufgerufen wird. Ich denke, es lohnt sich wahrscheinlich, Speicherlecks, die eine unbegrenzte Menge an Speicherplatz verbrauchen können, von Situationen zu unterscheiden, in denen nur eine begrenzte und vorhersehbare Menge an Speicherplatz verschwendet werden kann.
Supercat
@supercat: In meinem Kommentar ging es um Codeänderungen im Laufe der Zeit. Sicher, es ist kein Problem, eine begrenzte Menge an Speicher zu verlieren. Aber eines Tages wird jemand diese Funktion so ändern wollen, dass kein statischer Zeiger mehr verwendet wird. Wenn der Code nicht vorsieht, dass der Speicher, auf den verwiesen wird, freigegeben werden kann, ist dies eine schwierige Änderung (oder, schlimmer noch, die Änderung ist schlecht und Sie erhalten ein unbegrenztes Leck).
Adrian McCarthy
1
@AdrianMcCarthy: Wenn Sie den Code so ändern, dass kein statischer Zeiger mehr verwendet wird, müssen Sie den Zeiger wahrscheinlich in eine Art "Kontext" -Objekt verschieben und Code hinzufügen, um solche Objekte zu erstellen und zu zerstören. Wenn der Zeiger immer nulldann vorhanden ist, wenn keine Zuordnung vorhanden ist, und nicht null, wenn eine Zuordnung vorhanden nullist, wäre es unkompliziert, die Zuordnung durch Code freizugeben und den Zeiger auf den Zeitpunkt zu setzen, an dem ein Kontext zerstört wird, insbesondere im Vergleich zu allem anderen, was getan werden müsste statische Objekte in eine Kontextstruktur verschieben.
Supercat
52

Sie haben Recht, es wird kein Schaden angerichtet und es ist schneller, einfach zu beenden

Dafür gibt es verschiedene Gründe:

  • Alle Desktop- und Serverumgebungen geben beim Beenden () einfach den gesamten Speicherplatz frei. Sie kennen keine programminternen Datenstrukturen wie Heaps.

  • Fast alle free()Implementierungen geben ohnehin nie Speicher an das Betriebssystem zurück.

  • Noch wichtiger ist, dass es Zeitverschwendung ist, wenn Sie kurz vor dem Beenden () fertig sind. Beim Beenden werden Speicherseiten und Swap Space einfach freigegeben. Im Gegensatz dazu verbrennt eine Reihe von free () -Aufrufen die CPU-Zeit und kann zu Paging-Vorgängen auf der Festplatte, Cache-Fehlern und Cache-Räumungen führen.

In Bezug auf die Möglichkeit einer zukünftigen Wiederverwendung von Code, die die Gewissheit sinnloser Operationen rechtfertigt : Dies ist eine Überlegung, aber es ist wohl nicht der agile Weg. YAGNI!

DigitalRoss
quelle
2
Ich habe einmal an einem Projekt gearbeitet, bei dem wir einige Zeit damit verbracht haben, die Speichernutzung eines Programms zu verstehen (wir mussten es unterstützen, wir haben es nicht geschrieben). Aufgrund der Erfahrung stimme ich Ihrer zweiten Kugel anekdotisch zu. Ich würde jedoch gerne hören, dass Sie (oder jemand) mehr Beweise dafür liefern, dass dies wahr ist.
user106740
3
Egal , fand die Antwort: stackoverflow.com/questions/1421491/… . Vielen Dank!
user106740
@aviggiano, das heißt YAGNI.
v.oddou
Das YAGNI-Prinzip funktioniert in beide Richtungen: Sie müssen den Abschaltpfad nie optimieren. Vorzeitige Optimierungen und so weiter.
Adrian McCarthy
26

Ich bin völlig anderer Meinung als alle, die sagen, dass OP korrekt ist oder es keinen Schaden gibt.

Alle reden von einem modernen und / oder älteren Betriebssystem.

Aber was ist, wenn ich mich in einer Umgebung befinde, in der ich einfach kein Betriebssystem habe? Wo gibt es nichts?

Stellen Sie sich vor, Sie verwenden jetzt Interrupts im Thread-Stil und weisen Speicher zu. In der C-Norm ISO / IEC: 9899 wird die Lebensdauer des Speichers wie folgt angegeben:

7.20.3 Speicherverwaltungsfunktionen

1 Die Reihenfolge und Kontinuität des Speichers, der durch aufeinanderfolgende Aufrufe der Funktionen calloc, malloc und realloc zugewiesen wird, ist nicht angegeben. Der Zeiger, der zurückgegeben wird, wenn die Zuweisung erfolgreich ist, ist geeignet ausgerichtet, sodass er einem Zeiger auf einen beliebigen Objekttyp zugewiesen und dann verwendet werden kann, um auf ein solches Objekt oder ein Array solcher Objekte in dem zugewiesenen Bereich zuzugreifen (bis der Bereich explizit freigegeben wird). . Die Lebensdauer eines zugewiesenen Objekts erstreckt sich von der Zuweisung bis zur Freigabe. [...]

Es muss also nicht gegeben sein, dass die Umgebung den Befreiungsjob für Sie erledigt. Andernfalls würde es zum letzten Satz hinzugefügt: "Oder bis das Programm beendet wird."

Mit anderen Worten: Nicht Speicher freizugeben ist nicht nur eine schlechte Praxis. Es erzeugt nicht portablen und nicht C-konformen Code. Was zumindest als "richtig" angesehen werden kann, wenn Folgendes [...] von der Umgebung unterstützt wird ".

Aber in Fällen, in denen Sie überhaupt kein Betriebssystem haben, erledigt niemand die Arbeit für Sie (ich weiß, dass Sie im Allgemeinen keinen Speicher auf eingebetteten Systemen zuweisen und neu zuweisen, aber es gibt Fälle, in denen Sie dies möglicherweise möchten.)

Wenn man also im Allgemeinen C (als das OP gekennzeichnet ist) spricht, erzeugt dies einfach fehlerhaften und nicht portablen Code.

Dhein
quelle
4
Ein Gegenargument ist, dass Sie als Entwickler in Ihrer eingebetteten Umgebung in erster Linie weitaus anspruchsvoller in Ihrer Speicherverwaltung sind. Normalerweise ist dies so weit, dass der statische Festspeicher tatsächlich vorab zugewiesen wird, anstatt überhaupt Laufzeit-Mallocs / Reallocs zu haben.
John Go-Soco
1
@lunarplasma: Während das, was Sie sagen, nicht falsch ist, ändert dies nichts an der Tatsache, was der Sprachstandard angibt, und jeder, der dagegen handelt / es fördert, vielleicht sogar mit gesundem Menschenverstand, produziert begrenzten Code. Ich kann verstehen, wenn jemand sagt "Ich muss mich nicht darum kümmern", da es genug Fälle gibt, in denen es in Ordnung ist. ABER dass man zumindest wissen sollte, WARUM er sich nicht darum kümmern muss. und vor allem nicht auslassen, solange eine Frage nicht mit diesem Sonderfall zusammenhängt. Und da OP allgemein unter theoretischen (schulischen) Aspekten nach C fragt. Es ist nicht in Ordnung zu sagen "Sie müssen nicht"!
Dhein
2
In den meisten Umgebungen, in denen es kein Betriebssystem gibt, gibt es keine Mittel, über die Programme "beendet" werden können.
Supercat
@supercat: Wie ich schon geschrieben habe: Du hast recht damit. Aber wenn jemand in Bezug auf Unterrichtsgründe und schulische Aspekte danach fragt, ist es nicht richtig zu sagen: "Sie müssen die meiste Zeit nicht darüber nachdenken, es spielt keine Rolle." Der Wortlaut und das Verhalten der Sprache Die Definition wird aus einem bestimmten Grund angegeben, und nur weil die meisten Umgebungen dies für Sie erledigen, können Sie nicht sagen, dass Sie sich nicht darum kümmern müssen. Das ist mein Punkt.
Dhein
2
-1 für das Zitieren des C-Standards, während das meiste davon NICHT in Abwesenheit eines Betriebssystems gilt, da es keine Laufzeit gibt, um die Funktionen der Standardmandate bereitzustellen, insbesondere in Bezug auf die Speicherverwaltung und die Standardbibliotheksfunktionen (die offensichtlich ebenfalls fehlen) zusammen mit der Laufzeit / OS).
23

Normalerweise gebe ich jeden zugewiesenen Block frei, sobald ich sicher bin, dass ich damit fertig bin. Heute könnte der Einstiegspunkt meines Programms sein main(int argc, char *argv[]), aber morgen könnte es sein foo_entry_point(char **args, struct foo *f)und als Funktionszeiger eingegeben werden.

Wenn das passiert, habe ich jetzt ein Leck.

In Bezug auf Ihre zweite Frage würde ich, wenn mein Programm Eingaben wie a = 5 annehmen würde, Speicherplatz für a zuweisen oder denselben Speicherplatz für ein nachfolgendes a = "foo" neu zuweisen. Dies würde so lange zugewiesen bleiben, bis:

  1. Der Benutzer hat 'unset a' eingegeben.
  2. Meine Bereinigungsfunktion wurde eingegeben, entweder um ein Signal zu bedienen, oder der Benutzer gab "Beenden" ein.

Ich kann mir kein modernes Betriebssystem vorstellen, das nach dem Beenden eines Prozesses keinen Speicher zurückfordert. Andererseits ist free () billig, warum nicht aufräumen? Wie andere bereits gesagt haben, eignen sich Tools wie Valgrind hervorragend zum Erkennen von Lecks, über die Sie sich wirklich Sorgen machen müssen. Obwohl die von Ihnen beispielhaften Blöcke als "noch erreichbar" gekennzeichnet sind, ist dies nur ein zusätzliches Rauschen in der Ausgabe, wenn Sie sicherstellen möchten, dass keine Lecks vorhanden sind.

Ein anderer Mythos ist " Wenn es in main () ist, muss ich es nicht befreien ", ist dies falsch. Folgendes berücksichtigen:

char *t;

for (i=0; i < 255; i++) {
    t = strdup(foo->name);
    let_strtok_eat_away_at(t);
}

Wenn dies vor dem Forking / Daemonizing (und theoretisch für immer) geschehen ist, hat Ihr Programm gerade eine unbestimmte Größe von t 255 mal durchgesickert.

Ein gutes, gut geschriebenes Programm sollte immer nach sich selbst aufräumen. Geben Sie den gesamten Speicher frei, leeren Sie alle Dateien, schließen Sie alle Deskriptoren, entfernen Sie die Verknüpfung aller temporären Dateien usw. Diese Bereinigungsfunktion sollte bei normaler Beendigung oder beim Empfang verschiedener Arten von schwerwiegenden Signalen erreicht werden, es sei denn, Sie möchten einige Dateien herumliegen lassen, damit Sie können Erkennen Sie einen Absturz und fahren Sie fort.

Sei wirklich nett zu der armen Seele, die deine Sachen pflegen muss, wenn du zu anderen Dingen übergehst. Gib sie ihnen 'valgrind clean' :)

Tim Post
quelle
1
Und ja, ich hatte einmal einen Teamkollegen, der mir sagte: "Ich muss nie frei () in main () anrufen" <shudders>
Tim Post
free() is cheapWenn Sie nicht über eine Milliarde Datenstrukturen mit komplexen Beziehungen verfügen, die Sie einzeln freigeben müssen, kann das Durchlaufen der Datenstruktur, um zu versuchen, alles freizugeben, die Abschaltzeit erheblich verlängern, insbesondere wenn die Hälfte dieser Datenstruktur bereits ausgelagert ist auf die Festplatte, ohne Nutzen.
Lie Ryan
3
@LieRyan Wenn Sie eine Milliarde haben, wie in buchstäblich einer Milliarde Strukturen, haben Sie entschieden andere Probleme, die ein besonderes Maß an Berücksichtigung erfordern - weit über den Rahmen dieser speziellen Antwort hinaus :)
Tim Post
13

Es ist völlig in Ordnung, den Speicher beim Beenden frei zu lassen. malloc () ordnet den Speicher aus dem Speicherbereich zu, der als "Heap" bezeichnet wird, und der gesamte Heap eines Prozesses wird freigegeben, wenn der Prozess beendet wird.

Ein Grund, warum die Leute immer noch darauf bestehen, dass es gut ist, alles vor dem Beenden freizugeben, ist, dass Speicher-Debugger (z. B. valgrind unter Linux) die nicht freigegebenen Blöcke als Speicherlecks erkennen, und wenn Sie auch "echte" Speicherlecks haben, wird dies der Fall schwieriger zu erkennen, wenn Sie am Ende auch "falsche" Ergebnisse erhalten.

Antti Huima
quelle
1
Macht Valgrind nicht einen ziemlich guten Job und unterscheidet zwischen "durchgesickert" und "immer noch erreichbar"?
Christoffer
11
-1 für "völlig in Ordnung" Es ist eine schlechte Codierungspraxis, den zugewiesenen Speicher zu verlassen, ohne ihn freizugeben. Wenn dieser Code in eine Bibliothek extrahiert würde, würde er überall Memleaks verursachen.
DevinB
5
+1 zum Ausgleich. Siehe die Antwort von compie. freezur exitZeit als schädlich angesehen.
R .. GitHub STOP HELPING ICE
11

Wenn Sie den von Ihnen zugewiesenen Speicher verwenden, machen Sie nichts falsch. Es wird zu einem Problem, wenn Sie Funktionen (außer Hauptfunktionen) schreiben, die Speicher zuweisen, ohne ihn freizugeben und ohne ihn dem Rest Ihres Programms zur Verfügung zu stellen. Dann läuft Ihr Programm mit dem ihm zugewiesenen Speicher weiter, kann ihn jedoch nicht verwenden. Ihrem Programm und anderen laufenden Programmen wird dieser Speicher entzogen.

Bearbeiten: Es ist nicht 100% genau zu sagen, dass anderen laufenden Programmen dieser Speicher entzogen wird. Das Betriebssystem kann sie jederzeit auf Kosten des Austauschs Ihres Programms in den virtuellen Speicher ( </handwaving>) verwenden lassen. Der Punkt ist jedoch, dass ein virtueller Speicheraustausch weniger wahrscheinlich ist, wenn Ihr Programm Speicher freigibt, den es nicht verwendet.

Bill die Eidechse
quelle
11

Dieser Code funktioniert normalerweise einwandfrei, berücksichtigt jedoch das Problem der Wiederverwendung von Code.

Möglicherweise haben Sie ein Code-Snippet geschrieben, das den zugewiesenen Speicher nicht freigibt. Es wird so ausgeführt, dass der Speicher dann automatisch zurückgefordert wird. Scheint in Ordnung zu sein.

Dann kopiert jemand anderes Ihr Snippet so in sein Projekt, dass es tausendmal pro Sekunde ausgeführt wird. Diese Person hat jetzt einen großen Speicherverlust in ihrem Programm. Im Allgemeinen nicht sehr gut, normalerweise fatal für eine Serveranwendung.

Die Wiederverwendung von Code ist in Unternehmen typisch. Normalerweise besitzt das Unternehmen den gesamten Code, den seine Mitarbeiter produzieren, und jede Abteilung kann alles, was das Unternehmen besitzt, wiederverwenden. Wenn Sie also einen solchen "unschuldig aussehenden" Code schreiben, verursachen Sie anderen Menschen potenzielle Kopfschmerzen. Dies kann dazu führen, dass Sie gefeuert werden.

scharfer Zahn
quelle
2
Es kann erwähnenswert sein, dass nicht nur jemand das Snippet kopiert, sondern auch ein Programm, das geschrieben wurde, um eine bestimmte Aktion auszuführen, sobald es geändert wurde, um es wiederholt auszuführen. In einem solchen Fall wäre es in Ordnung, wenn der Speicher einmal zugewiesen und dann wiederholt verwendet würde, ohne jemals freigegeben zu werden, aber das Zuweisen und Verlassen des Speichers für jede Aktion (ohne ihn freizugeben) könnte katastrophal sein.
Supercat
7

Was ist das wirkliche Ergebnis hier?

Ihr Programm hat den Speicher verloren. Abhängig von Ihrem Betriebssystem wurde es möglicherweise wiederhergestellt.

Die meisten modernen Desktop- Betriebssysteme stellen bei Beendigung des Prozesses einen Speicherverlust wieder her, sodass das Problem leider häufig ignoriert wird, wie viele andere Antworten hier zeigen.)

Aber Sie auf ein Sicherheitsmerkmal setzen Sie sollten sich nicht darauf verlassen, und das Programm (oder Funktion) könnte auf einem System ausgeführt , wo dieses Verhalten tut Ergebnis in einem „harten“ Speicherleck, nächste Zeit.

Möglicherweise werden Sie im Kernel-Modus oder auf Vintage / Embedded-Betriebssystemen ausgeführt, die keinen Speicherschutz als Kompromiss verwenden. (MMUs beanspruchen den Speicherplatz, der Speicherschutz kostet zusätzliche CPU-Zyklen und es ist nicht zu viel, von einem Programmierer zu verlangen, nach sich selbst aufzuräumen).

Sie können den Speicher nach Belieben verwenden und wiederverwenden. Stellen Sie jedoch sicher, dass Sie alle Ressourcen freigegeben haben, bevor Sie ihn beenden.

DevSolar
quelle
5

Es gibt tatsächlich einen Abschnitt im OSTEP- Online-Lehrbuch für einen Bachelor-Kurs in Betriebssystemen, in dem genau Ihre Frage besprochen wird.

Der entsprechende Abschnitt ist "Vergessen des freien Speichers" im Kapitel " Speicher-API" auf Seite 6, der die folgende Erklärung enthält:

In einigen Fällen scheint es sinnvoll zu sein, free () nicht aufzurufen. Zum Beispiel ist Ihr Programm von kurzer Dauer und wird bald beendet. In diesem Fall bereinigt das Betriebssystem nach dem Abbruch des Prozesses alle zugewiesenen Seiten, sodass per se kein Speicherverlust auftritt. Während dies sicherlich „funktioniert“ (siehe Seite auf Seite 7), ist es wahrscheinlich eine schlechte Angewohnheit, sich zu entwickeln. Seien Sie also vorsichtig bei der Auswahl einer solchen Strategie

Dieser Auszug steht im Zusammenhang mit der Einführung des Konzepts des virtuellen Speichers. Grundsätzlich erklären die Autoren an dieser Stelle im Buch, dass eines der Ziele eines Betriebssystems darin besteht, "Speicher zu virtualisieren", dh jedes Programm glauben zu lassen, dass es Zugriff auf einen sehr großen Speicheradressraum hat.

Hinter den Kulissen übersetzt das Betriebssystem "virtuelle Adressen", die der Benutzer sieht, in tatsächliche Adressen, die auf den physischen Speicher verweisen.

Für die gemeinsame Nutzung von Ressourcen wie physischem Speicher muss das Betriebssystem jedoch nachverfolgen, welche Prozesse es verwenden. Wenn ein Prozess beendet wird, liegt es im Rahmen der Funktionen und Entwurfsziele des Betriebssystems, den Speicher des Prozesses zurückzugewinnen, damit er den Speicher neu verteilen und mit anderen Prozessen teilen kann.


BEARBEITEN: Die im Auszug erwähnte Seite wird unten kopiert.

AUSSERHALB: WARUM KEIN SPEICHER LECKT, WENN IHR PROZESS BEENDET WIRD

Wenn Sie ein kurzlebiges Programm schreiben, können Sie mit etwas Speicherplatz zuweisen malloc(). Das Programm wird ausgeführt und steht kurz vor dem Abschluss: free()Müssen Sie kurz vor dem Beenden einige Male aufrufen ? Obwohl es falsch erscheint, dies nicht zu tun, geht kein Gedächtnis im eigentlichen Sinne "verloren". Der Grund ist einfach: Es gibt wirklich zwei Ebenen der Speicherverwaltung im System. Die erste Ebene der Speicherverwaltung wird vom Betriebssystem ausgeführt, das Speicher an Prozesse weitergibt, wenn diese ausgeführt werden, und ihn zurücknimmt, wenn Prozesse beendet werden (oder auf andere Weise sterben). Die zweite Verwaltungsebene befindet sich in jedem Prozess, z. B. innerhalb des Heaps, wenn Sie malloc()und aufrufen free(). Auch wenn Sie nicht anrufenfree()(und damit Speicher im Heap verlieren), wird das Betriebssystem den gesamten Speicher des Prozesses (einschließlich der Seiten für Code, Stapel und, wie hier relevant, Heap) zurückfordern, wenn das Programm beendet ist. Unabhängig vom Status Ihres Heaps in Ihrem Adressraum nimmt das Betriebssystem alle diese Seiten zurück, wenn der Prozess abbricht, und stellt so sicher, dass kein Speicher verloren geht, obwohl Sie ihn nicht freigegeben haben.

Daher verursacht bei kurzlebigen Programmen ein Speicherverlust häufig keine Betriebsprobleme (obwohl dies als schlechte Form angesehen werden kann). Wenn Sie einen Server mit langer Laufzeit schreiben (z. B. einen Webserver oder ein Datenbankverwaltungssystem, das niemals beendet wird), ist Speicherverlust ein viel größeres Problem und führt schließlich zu einem Absturz, wenn der Anwendung der Speicher ausgeht. Und natürlich ist das Verlieren von Speicher ein noch größeres Problem innerhalb eines bestimmten Programms: des Betriebssystems selbst. Zeigen Sie uns noch einmal: Diejenigen, die den Kernel-Code schreiben, haben den härtesten Job von allen ...

ab Seite 7 des Kapitels Memory API von

Betriebssysteme: Drei einfache Teile
Remzi H. Arpaci-Dusseau und Andrea C. Arpaci-Dusseau Arpaci-Dusseau Bücher März 2015 (Version 0.90)

spenceryue
quelle
4

Es besteht keine wirkliche Gefahr , Ihre Variablen nicht freizugeben. Wenn Sie jedoch einen Zeiger auf einen Speicherblock einem anderen Speicherblock zuweisen, ohne den ersten Block freizugeben, ist der erste Block nicht mehr verfügbar, beansprucht jedoch weiterhin Speicherplatz. Dies wird als Speicherverlust bezeichnet. Wenn Sie dies regelmäßig tun, verbraucht Ihr Prozess immer mehr Speicher und entzieht anderen Prozessen Systemressourcen.

Wenn der Prozess nur von kurzer Dauer ist, können Sie dies häufig vermeiden, da der gesamte zugewiesene Speicher nach Abschluss des Prozesses vom Betriebssystem zurückgefordert wird. Ich würde jedoch empfehlen, sich daran zu gewöhnen, den gesamten Speicher freizugeben, für den Sie keine weitere Verwendung haben.

Kyle Cronin
quelle
1
Ich möchte -1 für Ihre erste Aussage "Es gibt keine Gefahr" sagen, außer dass Sie dann eine nachdenkliche Antwort darauf geben, warum es eine Gefahr gibt.
DevinB
2
Wenn die Gefahren gehen, ist es ziemlich harmlos - ich werde jeden Tag einen Speicherverlust über einen Segfault hinnehmen.
Kyle Cronin
1
Sehr wahr, und wir beide würden weder = D
DevinB
2
@KyleCronin Ich hätte viel lieber einen Segfault als einen Speicherverlust, da beide schwerwiegende Fehler sind und Segfaults leichter zu erkennen sind. Allzu oft bleiben Speicherlecks unbemerkt oder ungelöst, weil sie "ziemlich harmlos" sind. Mein RAM und ich sind uns nicht einig.
Dan Bechard
@ Dan Als Entwickler sicher. Als Benutzer nehme ich das Speicherleck. Ich hätte lieber Software, die funktioniert, wenn auch mit undichtem Speicher, als Software, die dies nicht tut.
Kyle Cronin
3

Sie sind in dieser Hinsicht absolut richtig. In kleinen trivialen Programmen, in denen eine Variable bis zum Tod des Programms vorhanden sein muss, hat die Freigabe des Speichers keinen wirklichen Vorteil.

Tatsächlich war ich einmal an einem Projekt beteiligt gewesen, bei dem jede Ausführung des Programms sehr komplex, aber relativ kurzlebig war, und die Entscheidung war, nur den zugewiesenen Speicher beizubehalten und das Projekt nicht zu destabilisieren, indem Fehler bei der Freigabe gemacht wurden.

Abgesehen davon ist dies in den meisten Programmen keine Option, oder es kann dazu führen, dass Ihnen der Speicher ausgeht.

Uri
quelle
2

Sie haben Recht, der Speicher wird automatisch freigegeben, wenn der Prozess beendet wird. Einige Leute bemühen sich, keine umfassende Bereinigung durchzuführen, wenn der Prozess beendet wird, da alles an das Betriebssystem abgegeben wird. Während Ihr Programm ausgeführt wird, sollten Sie jedoch nicht verwendeten Speicher freigeben. Wenn Sie dies nicht tun, kann es sein, dass Sie irgendwann ausgehen oder übermäßiges Paging verursachen, wenn Ihr Arbeitssatz zu groß wird.

Michael
quelle
2

Wenn Sie eine Anwendung von Grund auf neu entwickeln, können Sie fundierte Entscheidungen darüber treffen, wann Sie kostenlos anrufen möchten. Ihr Beispielprogramm ist in Ordnung: Es weist Speicher zu, möglicherweise funktioniert es einige Sekunden lang und wird dann geschlossen, wodurch alle beanspruchten Ressourcen freigegeben werden.

Wenn Sie jedoch noch etwas anderes schreiben - einen Server / eine lang laufende Anwendung oder eine Bibliothek, die von jemand anderem verwendet werden soll, sollten Sie damit rechnen, bei allem, was Sie tun, kostenlos anzurufen.

Wenn Sie die pragmatische Seite für eine Sekunde ignorieren, ist es viel sicherer, dem strengeren Ansatz zu folgen und sich zu zwingen, alles zu befreien, was Sie tun. Wenn Sie nicht die Gewohnheit haben, beim Codieren auf Speicherlecks zu achten, können leicht einige Lecks auftreten. Mit anderen Worten, ja - Sie können ohne sie davonkommen; Bitte seien Sie jedoch vorsichtig.

ojrac
quelle
0

Wenn ein Programm vergisst, einige Megabyte freizugeben, bevor es beendet wird, werden sie vom Betriebssystem freigegeben. Wenn Ihr Programm jedoch wochenlang ausgeführt wird und eine Schleife im Programm vergisst, in jeder Iteration einige Bytes freizugeben, tritt ein schwerwiegender Speicherverlust auf, der den gesamten verfügbaren Speicher Ihres Computers aufzehrt, sofern Sie ihn nicht regelmäßig neu starten base => Selbst kleine Speicherlecks können schlecht sein, wenn das Programm für eine ernsthafte große Aufgabe verwendet wird, auch wenn es ursprünglich nicht für eine solche Aufgabe ausgelegt war.

Gunter Königsmann
quelle
-2

Ich denke, dass Ihre beiden Beispiele eigentlich nur eines sind: Das free()sollte nur am Ende des Prozesses auftreten, was, wie Sie betonen, nutzlos ist, da der Prozess beendet wird.

In Ihrem zweiten Beispiel besteht der einzige Unterschied darin, dass Sie eine undefinierte Anzahl von zulassen malloc(), was dazu führen kann, dass der Speicher knapp wird. Die einzige Möglichkeit, mit der Situation umzugehen, besteht darin, den Rückkehrcode von zu überprüfen malloc()und entsprechend zu handeln.

Mouviciel
quelle