Wenn ich während der gesamten Lebensdauer meines Programms ein Stück Speicherplatz benötige, muss ich ihn dann wirklich kurz vor dem Beenden des Programms freigeben?

67

In vielen Büchern und Tutorials wurde das Speichermanagement betont und ich hatte das Gefühl, dass einige mysteriöse und schreckliche Dinge passieren würden, wenn ich den Speicher nicht freigeben würde, nachdem ich ihn nicht mehr benutze.

Ich kann nicht für andere Systeme sprechen (obwohl es für mich vernünftig ist anzunehmen, dass sie eine ähnliche Vorgehensweise anwenden), aber zumindest unter Windows kann der Kernel die meisten Ressourcen (mit Ausnahme einiger weniger) bereinigen, die von verwendet werden ein Programm nach Programmende. Dazu gehört unter anderem Heap-Speicher.

Ich verstehe, warum Sie eine Datei schließen möchten, nachdem Sie sie verwendet haben, um sie dem Benutzer zur Verfügung zu stellen, oder warum Sie einen mit einem Server verbundenen Socket trennen möchten, um Bandbreite zu sparen Sie müssen ALLEN von Ihrem Programm verwendeten Speicher verwalten.

Jetzt bin ich damit einverstanden, dass diese Frage weit gefasst ist, da der Umgang mit Ihrem Speicher davon abhängt, wie viel Speicher Sie benötigen und wann Sie ihn benötigen. Daher werde ich den Umfang dieser Frage auf Folgendes eingrenzen : Wenn ich ein Stück von verwenden muss Ist es wirklich notwendig, den Speicher während der gesamten Lebensdauer meines Programms freizugeben, bevor das Programm beendet wird?

Bearbeiten: Die als Duplikat vorgeschlagene Frage war spezifisch für die Unix-Betriebssystemfamilie. Die beste Antwort war sogar ein Linux-spezifisches Tool (z. B. Valgrind). Diese Frage bezieht sich auf die meisten "normalen" nicht eingebetteten Betriebssysteme und darauf, warum es sinnvoll ist, Speicher freizugeben, der während der gesamten Lebensdauer eines Programms benötigt wird.

Kapitän offensichtlich
quelle
19
Sie haben diese Sprache als Agnostiker markiert, aber in vielen Sprachen (z. B. Java) ist es nicht erforderlich, Speicherplatz manuell freizugeben. Es geschieht automatisch einige Zeit, nachdem der letzte Verweis auf ein Objekt den Gültigkeitsbereich
verlässt
3
und natürlich können Sie C ++ schreiben, ohne einen einzigen Löschvorgang auszuführen, und es wäre in Ordnung für den Speicher
Nikko
5
@RichardTingle Obwohl ich mir keine andere Sprache außer C und vielleicht C ++ vorstellen kann, sollte das sprachunabhängige Tag alle Sprachen abdecken, in denen nicht viele Speicherbereinigungs-Dienstprogramme integriert sind. Das ist heutzutage freilich selten. Sie könnten argumentieren, dass Sie ein solches System jetzt in C ++ implementieren können, aber letztendlich haben Sie immer noch die Wahl, ein Stück Speicher nicht zu löschen.
CaptainObvious
4
Sie schreiben: "Der Kernel ist grundsätzlich [...] dafür verantwortlich, alle Ressourcen nach Programmende zu bereinigen". Dies ist im Allgemeinen falsch, da nicht alles Speicher, ein Handle oder ein Kernel-Objekt ist. Dies gilt jedoch für das Gedächtnis, auf das Sie Ihre Frage sofort beschränken.
Eric Towers

Antworten:

108

Wenn ich während der gesamten Lebensdauer meines Programms ein Stück Speicherplatz benötige, muss ich ihn dann wirklich kurz vor dem Beenden des Programms freigeben?

Es ist nicht obligatorisch, kann jedoch Vor- und Nachteile haben.

Wenn das Programm den Speicher während seiner Ausführungszeit einmal reserviert und ihn ansonsten erst am Ende des Prozesses freigibt, ist es möglicherweise sinnvoll, den Speicher nicht manuell freizugeben und sich auf das Betriebssystem zu verlassen. Auf jedem modernen Betriebssystem, das ich kenne, ist dies sicher. Am Ende des Prozesses wird der gesamte zugewiesene Speicher zuverlässig an das System zurückgegeben.
In einigen Fällen kann es sogar erheblich schneller sein, den zugewiesenen Speicher nicht explizit zu bereinigen als die Bereinigung.

Indem Sie jedoch den gesamten Speicher am Ende der Ausführung explizit freigeben,

  • Während des Debuggens / Testens zeigen Tools zur Erkennung von Mem-Lecks keine "False Positives" an.
  • Es ist möglicherweise viel einfacher, den Code, der den Speicher zusammen mit der Zuordnung und Freigabe verwendet, in eine separate Komponente zu verschieben und ihn später in einem anderen Kontext zu verwenden, in dem die Verwendungsdauer für den Speicher vom Benutzer der Komponente gesteuert werden muss

Die Lebensdauer von Programmen kann sich ändern. Vielleicht ist Ihr Programm heute ein kleines Befehlszeilendienstprogramm mit einer typischen Lebensdauer von weniger als 10 Minuten, und es weist alle 10 Sekunden Speicher in Teilen von einigen KB zu - Sie müssen also vor dem Ende des Programms keinen zugewiesenen Speicher mehr freigeben. Später wird das Programm geändert und im Rahmen eines Serverprozesses mit einer Lebensdauer von mehreren Wochen erweitert. Daher ist es keine Option mehr, nicht genutzten Speicher dazwischen freizugeben, da Ihr Programm sonst mit der Zeit den gesamten verfügbaren Serverspeicher verbraucht . Dies bedeutet, dass Sie das gesamte Programm überprüfen und anschließend den Freigabecode hinzufügen müssen. Wenn Sie Glück haben, ist dies eine leichte Aufgabe. Wenn nicht, kann es so schwierig sein, dass die Chancen hoch sind, dass Sie einen Platz verpassen. Und wenn Sie in dieser Situation sind, werden Sie sich wünschen, Sie hätten das "freie" hinzugefügt

Generell gilt, dass das paarweise Schreiben von Zuweisungs- und zugehörigen Freigabecodes bei vielen Programmierern als "gute Gewohnheit" gilt: Auf diese Weise verringern Sie immer die Wahrscheinlichkeit, dass Sie den Freigabecode in Situationen vergessen, in denen der Speicher freigegeben werden muss .

Doc Brown
quelle
12
Ein guter Punkt, um gute Programmiergewohnheiten zu entwickeln.
Lawrence
4
Generell stimme ich diesem Rat sehr zu. Gute Gewohnheiten im Gedächtnis zu behalten ist sehr wichtig. Ich glaube jedoch, dass es Ausnahmen gibt. Wenn Sie beispielsweise einen verrückten Graphen zugewiesen haben, dessen Durchqueren und Freigeben einige Sekunden in Anspruch nehmen wird, können Sie die Benutzerfreundlichkeit verbessern, indem Sie das Programm beenden und das Betriebssystem einen Nuke ausführen lassen.
GrandOpener
1
Es ist sicher auf jedem modernen "normalen" (nicht eingebetteten mit Speicherschutz) Betriebssystem und wäre ein schwerwiegender Fehler, wenn es nicht wäre. Wenn ein nicht privilegierter Prozess dauerhaft Speicher verlieren kann, den das Betriebssystem nicht zurückerhält, kann das System durch mehrmaliges Ausführen über genügend Speicher verfügen. Die langfristige Gesundheit des Betriebssystems kann nicht von fehlenden Fehlern in nicht privilegierten Programmen abhängen. (Dies ignoriert natürlich Dinge wie Unix-Shared-Memory-Segmente, die bestenfalls durch Swap Space gesichert sind.)
Peter Cordes
7
@GrandOpener In diesem Fall bevorzugen Sie möglicherweise eine Art region-basierten Allokator für diesen Baum, damit Sie ihn wie gewohnt zuweisen und nur die Zuordnung der gesamten Region auf einmal aufheben können, anstatt ihn zu durchlaufen und freizugeben es Stück für Stück. Das ist immer noch "richtig".
Thomas
3
Wenn der Speicher wirklich für die gesamte Lebensdauer des Programms vorhanden sein muss, ist es möglicherweise eine sinnvolle Alternative, eine Struktur auf dem Stapel zu erstellen, z main. B. in .
Kyle Strand
11

Das Freigeben von Speicher am Ende einer Programmausführung ist nur eine Verschwendung von CPU-Zeit. Es ist, als würde man ein Haus aufräumen, bevor man es aus dem Orbit entfernt.

Manchmal kann sich jedoch aus einem kurzfristig laufenden Programm ein Teil eines viel länger laufenden Programms entwickeln. Dann wird es notwendig, Dinge zu befreien. Wenn dies zumindest teilweise nicht berücksichtigt wurde, kann es zu erheblichen Nacharbeiten kommen.

Eine clevere Lösung hierfür ist "Talloc", mit der Sie eine Menge Speicherzuweisungen vornehmen und diese dann mit einem Aufruf wegwerfen können.

Peter Green
quelle
1
Vorausgesetzt, Sie wissen, dass Ihr Betriebssystem keine eigenen Lecks aufweist.
WGroleau
5
"Verschwendung von CPU-Zeit" Obwohl sehr, sehr wenig Zeit. freeist normalerweise viel schneller als malloc.
Paul Draper
@PaulDraper Ja, Zeitverschwendung, alles wieder einzutauschen, ist viel bedeutender.
Deduplizierer
5

Sie könnten eine Sprache mit Garbage Collection verwenden (wie Scheme, Ocaml, Haskell, Common Lisp und sogar Java, Scala, Clojure).

(In den meisten GC-ed-Sprachen gibt es keine Möglichkeit , Speicher explizit und manuell freizugeben. Manchmal wurden einige Werte möglicherweise finalisiert , z. B. schlossen der GC und das Laufzeitsystem einen Datei-Handle-Wert, wenn dieser Wert nicht erreichbar war. Dies ist jedoch nicht der Fall sicher, unzuverlässig, und Sie sollten stattdessen Ihre Datei-Handles explizit schließen , da die Finalisierung niemals garantiert wird.)

Sie können für Ihr in C (oder sogar C ++) codiertes Programm auch den konservativen Müllsammler von Boehm verwenden . Sie würden dann alle Ihre mallocdurch ersetzen GC_malloc und sich nicht darum freekümmern, irgendeinen Zeiger zu verwenden. Natürlich müssen Sie die Vor- und Nachteile der Verwendung von Böhms GC verstehen. Lesen Sie auch das GC-Handbuch .

Speicherverwaltung ist eine globale Eigenschaft eines Programms. In gewisser Weise ist es (und die Aktualität einiger gegebener Daten) nicht kompositionell und nicht modular, da es sich um eine ganze Programmeigenschaft handelt.

Wie bereits erwähnt, freeempfiehlt es sich, die C-Speicherzone Ihres Heapspeichers explizit zuzuweisen. Für ein Spielzeugprogramm, das nicht viel Speicherplatz belegt, können Sie sich sogar dafür entscheiden, überhaupt keinen freeSpeicherplatz zu belegen (da nach Beendigung des Prozesses die Ressourcen einschließlich des virtuellen Adressraums vom Betriebssystem freigegeben werden).

Wenn ich während der gesamten Lebensdauer meines Programms ein Stück Speicherplatz benötige, muss ich ihn dann wirklich kurz vor dem Beenden des Programms freigeben?

Nein, das musst du nicht. Und viele reale Programme machen sich nicht die Mühe, Speicherplatz freizugeben, der für die gesamte Lebensdauer benötigt wird (insbesondere der GCC- Compiler gibt einen Teil seines Speichers nicht frei). Wenn Sie dies jedoch tun (z. B. freebestimmte dynamisch zugewiesene C- Daten nicht stören ), sollten Sie dies besser kommentieren , um die Arbeit zukünftiger Programmierer für dasselbe Projekt zu vereinfachen . Ich neige dazu, zu empfehlen, dass die Menge des nicht freigegebenen Speichers begrenzt bleibt und normalerweise relativ klein ist, um den gesamten verwendeten Heap-Speicher zu belegen.

Beachten Sie, dass das System freehäufig keinen Speicher für das Betriebssystem freigibt (z. B. durch Aufrufen von munmap (2) auf POSIX-Systemen), sondern in der Regel eine Speicherzone als für die Zukunft wiederverwendbar markiert malloc. Insbesondere der virtuelle Adressraum (z. B. /proc/self/mapsunter Linux, siehe proc (5) ....) wird möglicherweise nicht verkleinert free(daher geben Dienstprogramme dieselbe Menge an verwendetem Speicher für Ihren Prozess an psoder topmelden dies).

Basile Starynkevitch
quelle
3
"Manchmal können einige Werte finalisiert werden, z. B. der GC und das Laufzeitsystem schließen einen Datei-Handle-Wert, wenn dieser Wert nicht erreichbar ist." jede Finalizerthread jemals laufen, auch bei der Ausfahrt: stackoverflow.com/questions/7880569/...
Deduplicator
Ich persönlich bevorzuge diese Antwort, da sie uns dazu ermutigt, GC (dh einen stärker automatisierten Ansatz) zu verwenden.
Ta Thanh Dinh
3
@tathanhdinh Automatisierter, jedoch ausschließlich für den Speicher. Vollständig manuell für alle anderen Ressourcen. GC handelt von Determinismus und Gedächtnis für eine bequeme Handhabung des Gedächtnisses. Und nein, Finalisten helfen nicht viel und haben ihre eigenen Probleme.
Deduplizierer
3

Es ist nicht notwendig , da Sie nicht versäumen werden, Ihr Programm ordnungsgemäß auszuführen, wenn Sie dies nicht tun. Es gibt jedoch Gründe, die Sie wählen können, wenn Sie eine Chance haben.

Einer der mächtigsten Fälle, auf die ich stoße (immer und immer wieder), ist, dass jemand ein kleines Stück Simulationscode schreibt, das in seiner ausführbaren Datei ausgeführt wird. Sie sagen "Wir möchten, dass dieser Code in die Simulation integriert wird." Ich frage sie dann, wie sie vorhaben, zwischen den Monte-Carlo-Läufen eine Neuinitialisierung durchzuführen, und sie schauen mich verständnislos an. "Was meinst du mit Neuinitialisierung? Sie führen das Programm nur mit neuen Einstellungen aus?"

Manchmal erleichtert eine saubere Bereinigung die Verwendung Ihrer Software erheblich . Bei vielen Beispielen wird davon ausgegangen, dass Sie niemals etwas bereinigen müssen, und Sie treffen Annahmen darüber, wie Sie mit den Daten und ihrer Lebensdauer um diese Annahmen herum umgehen können. Wenn Sie in eine neue Umgebung wechseln, in der diese Annahmen ungültig sind, funktionieren möglicherweise keine vollständigen Algorithmen mehr.

Ein Beispiel dafür, wie seltsam es werden kann, finden Sie in der Beschreibung, wie die verwalteten Sprachen mit der Finalisierung am Ende von Prozessen umgehen oder wie C # mit dem programmgesteuerten Anhalten von Anwendungsdomänen umgeht. Sie knüpfen sich aneinander, weil in diesen Extremfällen Annahmen durch die Risse fallen.

Cort Ammon
quelle
1

Ignorieren von Sprachen, in denen Sie Speicher nicht manuell freigeben ...

Was Sie momentan als "Programm" betrachten, könnte irgendwann zu einer Funktion oder Methode werden, die Teil eines größeren Programms ist. Und dann wird diese Funktion möglicherweise mehrmals aufgerufen. Und dann ist der Speicher, den Sie "manuell freigeben" sollten, ein Speicherverlust. Das ist natürlich ein Urteilsspruch.

gnasher729
quelle
2
dies scheint lediglich den Punkt zu wiederholen, der in der Antwort , die einige Stunden zuvor gepostet wurde, gemacht (und viel besser erklärt) wurde: "Es könnte viel einfacher sein, den Code, der den Speicher zusammen mit der Zuordnung und Freigabe verwendet, in eine separate Komponente zu verschieben und ihn später zu verwenden in einem anderen Kontext, in dem die Nutzungszeit für den Speicher vom Benutzer der Komponente gesteuert werden muss ... "
gnat
1

Weil es sehr wahrscheinlich ist, dass Sie Ihr Programm irgendwann ändern, es in etwas anderes integrieren, mehrere Instanzen nacheinander oder parallel ausführen möchten. Dann muss dieser Speicher manuell freigegeben werden - aber Sie werden sich nicht mehr an die Umstände erinnern und es kostet Sie viel mehr Zeit, Ihr Programm neu zu verstehen.

Tun Sie Dinge, während Ihr Verständnis noch frisch ist.

Es ist eine kleine Investition, die in Zukunft hohe Renditen bringen kann.

Agent_L
quelle
2
Dies scheint nichts Wesentliches über die in den vorherigen 11 Antworten gemachten und erklärten Punkte hinzuzufügen. Insbesondere wurde bereits drei- oder viermal auf eine mögliche Änderung des Programms
hingewiesen
1
@gnat Auf verworrene und undurchsichtige Weise - ja. In einer klaren Aussage - nein. Konzentrieren wir uns auf die Qualität der Antworten, nicht auf die Schnelligkeit.
Agent_L
1
Qualität weise, Top-Antwort scheint zu erklären diesen Punkt viel besser zu sein
gnat
1

Nein, das ist nicht nötig, aber eine gute Idee.

Sie sagen, dass Sie das Gefühl haben, dass "mysteriöse und schreckliche Dinge passieren würden, wenn ich den Speicher nicht freimachen würde, nachdem ich damit fertig bin".

Aus technischer Sicht ist die einzige Konsequenz daraus, dass Ihr Programm so lange mehr Speicher verbraucht, bis entweder eine harte Grenze erreicht ist (z. B. Ihr virtueller Adressraum erschöpft ist) oder die Leistung inakzeptabel wird. Wenn das Programm beendet werden soll, spielt dies keine Rolle, da der Prozess praktisch nicht mehr vorhanden ist. Bei den "mysteriösen und schrecklichen Dingen" geht es nur um den mentalen Zustand des Entwicklers. Das Auffinden der Ursache eines Speicherverlusts kann ein absoluter Albtraum sein (dies ist eine Untertreibung) und es erfordert viel Geschick und Disziplin, um leckfreien Code zu schreiben. Der empfohlene Ansatz zur Entwicklung dieser Fertigkeiten und Disziplinen besteht darin, immer Speicher freizugeben, wenn er nicht mehr benötigt wird, auch wenn das Programm kurz vor dem Ende steht.

Dies hat natürlich den zusätzlichen Vorteil, dass Ihr Code einfacher wiederverwendet und angepasst werden kann, wie andere bereits gesagt haben.

Allerdings gibt es mindestens einen Fall , in dem es besser ist , nicht um Speicher zu frei kurz vor Beendigung des Programms.

Stellen Sie sich den Fall vor, dass Sie Millionen kleiner Zuordnungen vorgenommen haben und diese größtenteils auf die Festplatte ausgelagert wurden. Wenn Sie anfangen, alles freizugeben, muss der größte Teil Ihres Speichers wieder in den Arbeitsspeicher übertragen werden, damit auf die Buchhaltungsinformationen zugegriffen werden kann, um die Daten sofort zu verwerfen. Dadurch kann es einige Minuten dauern, bis das Programm beendet ist! Ganz zu schweigen davon, dass in dieser Zeit viel Druck auf die Festplatte und den physischen Speicher ausgeübt wird. Wenn der physische Speicher zu Beginn knapp ist (möglicherweise wird das Programm geschlossen, weil ein anderes Programm viel Speicher verbraucht), müssen einzelne Seiten möglicherweise mehrmals ein- und ausgelagert werden, wenn mehrere Objekte aus dem Speicher freigegeben werden müssen gleiche Seite, werden aber nicht nacheinander freigegeben.

Wenn das Programm stattdessen nur abgebrochen wird, verwirft das Betriebssystem einfach den gesamten Speicher, der auf die Festplatte ausgelagert wurde. Dies erfolgt fast augenblicklich, da überhaupt kein Zugriff auf die Festplatte erforderlich ist.

Es ist wichtig zu beachten, dass in einer OO-Sprache der Aufruf des Destruktors eines Objekts auch das Austauschen des Speichers erzwingt. Wenn Sie dies tun müssen, können Sie auch den Speicher freigeben.

Artelius
quelle
1
Können Sie etwas anführen, das die Idee stützt, dass ausgelagerter Speicher eingelagert werden muss, um die Zuordnung aufzuheben? Das ist offensichtlich ineffizient. Sicher sind moderne Betriebssysteme schlauer als das!
1
@Jon of All Trades, moderne Betriebssysteme sind schlau, ironischerweise sind es die meisten modernen Sprachen nicht. Wenn Sie (etwas) freigeben, legt der Compiler "etwas" auf den Stapel und ruft dann free () auf. Um es auf den Stapel zu legen, muss es nach dem Auslagern wieder in den Arbeitsspeicher verschoben werden.
Jeffiekins
@ JonofAllTrades eine freie Liste hätte diesen Effekt, das ist allerdings eine ziemlich veraltete Malloc-Implementierung. Das Betriebssystem kann Sie jedoch nicht davon abhalten, eine solche Implementierung zu verwenden.
0

Die Hauptgründe für die manuelle Bereinigung sind: Es ist weniger wahrscheinlich, dass Sie ein echtes Speicherverlust für einen Speicherblock halten, der nur beim Beenden bereinigt wird. Manchmal treten nur dann Fehler im Allokator auf, wenn Sie die gesamte Datenstruktur durchsuchen ausplanen es, und Sie werden einen viel kleineren Kopfschmerzen haben , wenn Sie jemals so umgestalten , dass ein Objekt nicht vor dem Programm beendet erhalten ausgeplant müssen.

Die Hauptgründe, warum Sie nicht manuell bereinigen sollten, sind: Leistung, Leistung, Leistung und die Möglichkeit, dass Ihr unnötiger Bereinigungscode einen Fehler enthält, der sich in einen Absturzfehler oder eine Sicherheitslücke verwandeln kann Ausbeuten.

Wenn Sie Wert auf Leistung legen, möchten Sie immer ein Profil erstellen, um herauszufinden, wo Sie Ihre ganze Zeit verschwenden, anstatt zu raten. Ein möglicher Kompromiss besteht darin, Ihren optionalen Bereinigungscode in einen bedingten Block zu packen, ihn beim Debuggen zu belassen, damit Sie die Vorteile des Schreibens und Debuggens des Codes nutzen können, und dann, wenn Sie empirisch festgestellt haben, dass es zu viel ist Weisen Sie den Compiler an, es in Ihrer endgültigen ausführbaren Datei zu überspringen. Und eine Verzögerung beim Abschluss des Programms ist per definitionem kaum auf dem kritischen Pfad.

Davislor
quelle