it-swarm.com.de

Wenn ich während der gesamten Lebensdauer meines Programms ein Stück Speicher verwenden muss, muss es dann wirklich vor dem Beenden des Programms freigegeben werden?

In vielen Büchern und Tutorials habe ich die Praxis des Speichermanagements betont und das Gefühl gehabt, dass einige mysteriöse und schreckliche Dinge passieren würden, wenn ich den Speicher nicht freigeben würde, nachdem ich ihn verwendet habe.

Ich kann nicht für andere Systeme sprechen (obwohl es für mich vernünftig ist anzunehmen, dass sie eine ähnliche Praxis anwenden), aber zumindest unter Windows wird vom Kernel garantiert, dass die meisten Ressourcen (mit Ausnahme einiger weniger), die von verwendet werden, bereinigt werden ein Programm nach Programmbeendigung. Dazu gehört unter anderem Heapspeicher.

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 an einen Server angeschlossenen Socket trennen möchten, um Bandbreite zu sparen, aber das scheint albern Sie müssen Ihren gesamten von Ihrem Programm verwendeten Speicher mikromanagen.

Nun stimme ich zu, dass diese Frage weit gefasst ist, da die Art und Weise, wie Sie mit Ihrem Speicher umgehen sollen, davon abhängt, wie viel Speicher Sie benötigen und wann Sie ihn benötigen. Daher werde ich den Umfang dieser Frage auf Folgendes beschränken: Wenn ich muss Verwenden Sie während der gesamten Lebensdauer meines Programms ein Stück Speicher. Ist es wirklich erforderlich, es unmittelbar vor dem Beenden des Programms freizugeben?

Bearbeiten: Die als Duplikat vorgeschlagene Frage war spezifisch für die Unix-Betriebssystemfamilie. In der Top-Antwort wurde sogar ein Linux-spezifisches Tool angegeben (z. B. Valgrind). Diese Frage soll die meisten "normalen" nicht eingebetteten Betriebssysteme abdecken und warum es eine gute Praxis ist oder nicht, Speicher freizugeben, der während der gesamten Lebensdauer eines Programms benötigt wird.

63
CaptainObvious

Wenn ich während der gesamten Lebensdauer meines Programms ein Stück Speicher verwenden muss, muss es dann wirklich vor dem Beenden des Programms freigegeben werden?

Es ist nicht obligatorisch, kann aber Vorteile (sowie einige Nachteile) haben.

Wenn das Programm während seiner Ausführungszeit einmal Speicher zuweist und ihn ansonsten erst nach Beendigung 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 wesentlich schneller sein, den zugewiesenen Speicher nicht explizit zu bereinigen, als die Bereinigung durchzuführen.

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

  • während des Debuggens/Testens zeigen Mem-Leckerkennungs-Tools keine "False Positives" an.
  • es ist möglicherweise viel einfacher, den Code, der den Speicher zusammen mit Zuweisung und Freigabe verwendet, in eine separate Komponente zu verschieben und später in einem anderen Kontext zu verwenden, in dem die Nutzungszeit für den Speicher vom Benutzer der Komponente gesteuert werden muss

Die Lebensdauer von Programmen kann sich ändern. Möglicherweise handelt es sich bei Ihrem Programm heute um ein kleines Befehlszeilenprogramm mit einer typischen Lebensdauer von weniger als 10 Minuten. Alle 10 Sekunden wird Speicher in Teilen von einigen KB zugewiesen. Sie müssen also vor Programmende keinen zugewiesenen Speicher freigeben. Später wird das Programm geändert und im Rahmen eines Serverprozesses mit einer Lebensdauer von mehreren Wochen erweitert. Daher ist es nicht mehr möglich, nicht genutzten Speicher dazwischen freizugeben. Andernfalls wird Ihr Programm im Laufe der Zeit den gesamten verfügbaren Serverspeicher verbrauchen . Dies bedeutet, dass Sie das gesamte Programm überprüfen und anschließend den Code für die Freigabe 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 sich in dieser Situation befinden, möchten Sie, dass Sie den "kostenlosen" Code zuvor zu Ihrem Programm hinzugefügt haben, als Sie den "malloc" -Code hinzugefügt haben.

Im Allgemeinen gilt das Schreiben von zuweisendem und zugehörigem Freigabecode für viele Programmierer immer paarweise als "gute Angewohnheit": Wenn Sie dies immer tun, verringern Sie die Wahrscheinlichkeit, den Freigabecode in Situationen zu vergessen, in denen der Speicher muss sein muss befreit.

106
Doc Brown

Das Freigeben von Speicher am Ende eines Programmlaufs ist nur eine Verschwendung von CPU-Zeit. Es ist, als würde man ein Haus aufräumen, bevor man es aus der Umlaufbahn bringt.

Manchmal kann ein kurz laufendes Programm jedoch Teil eines viel länger laufenden Programms werden. 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 Speicherzuordnungen vornehmen und diese dann alle mit einem Anruf wegwerfen können.

11
Peter Green

Sie können eine Sprache mit Garbage Collection verwenden (z. B. Scheme, Ocaml, Haskell, Common LISP und sogar Java, Scala, Clojure).

(In den meisten GC-ed-Sprachen gibt es auf keinen Fall bis explizit und manuell freien Speicher! Manchmal können einige Werte finalisiert sein, z. Der GC und das Laufzeitsystem würden einen Dateihandle-Wert schließen, wenn dieser Wert nicht erreichbar ist. Dies ist jedoch nicht sicher, unzuverlässig, und Sie sollten stattdessen explizit Ihre Dateihandles schließen, da die Finalisierung niemals garantiert wird.)

Sie können für Ihr in C (oder sogar C++) codiertes Programm auch Böhms konservativer Garbage Collector verwenden. Sie würden dann alle Ihre malloc durch GC_malloc Ersetzen und sich nicht darum kümmern, free einen Zeiger zu verwenden. Natürlich müssen Sie die Vor- und Nachteile der Verwendung von Boehms GC verstehen. Lesen Sie auch das GC-Handbuch .

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

Wie andere bereits betont haben, ist es eine gute Praxis, explizit free Ihre Heap-zugewiesene C-Speicherzone zu verwenden. Für ein Spielzeugprogramm, das nicht viel Speicher reserviert, können Sie sogar entscheiden, überhaupt keinen free Speicher zu verwenden (seitdem der Prozess beendet ist, werden seine Ressourcen, einschließlich seines virtueller Adressraum , würde vom Betriebssystem freigegeben).

Wenn ich während der gesamten Lebensdauer meines Programms ein Stück Speicher verwenden muss, muss es dann wirklich vor dem Beenden des Programms freigegeben werden?

Nein, das musst du nicht tun. Und viele Programme in der realen Welt machen sich nicht die Mühe, Speicher freizugeben, der während der gesamten Lebensdauer benötigt wird (insbesondere der GCC Compiler gibt einen Teil seines Speichers nicht frei). . Wenn Sie dies jedoch tun (z. B. wenn Sie sich nicht die Mühe machen, free - ein bestimmtes Stück C dynamisch zugewiesen Daten) zu verwenden, sind Sie besser kommentieren diese Tatsache, um die Arbeit zukünftiger Programmierer zu erleichtern zum selben Projekt. Ich neige dazu zu empfehlen, dass die Menge an nicht freigegebenem Speicher begrenzt bleibt und normalerweise relativ klein ist. zum gesamten verwendeten Heapspeicher.

Beachten Sie, dass das System free häufig keinen Speicher für das Betriebssystem freigibt (z. B. durch Aufrufen von munmap (2) auf POSIX-Systemen), sondern normalerweise eine Speicherzone als für zukünftige malloc. Insbesondere der virtuelle Adressraum (z. B. durch /proc/self/maps Unter Linux, siehe proc (5) ....) wird möglicherweise nach free nicht verkleinert (daher Dienstprogramme wie ps oder top melden dieselbe Menge an verwendetem Speicher für Ihren Prozess.

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

Einer der mächtigsten Fälle, auf die ich stoße (immer und immer wieder), ist, dass jemand einen kleinen Teil des Simulationscodes schreibt, der 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 Monte-Carlo-Läufen neu zu initialisieren, und sie sehen mich verständnislos an. "Was meinst du mit Neuinitialisierung? Sie führen das Programm nur mit neuen Einstellungen aus?"

Manchmal erleichtert eine Bereinigung die Verwendung Ihrer Software viel. Bei vielen Beispielen nehmen Sie an, dass Sie niemals etwas bereinigen müssen, und Sie gehen davon aus, 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 nicht gültig sind, funktionieren möglicherweise nicht mehr ganze Algorithmen.

Ein Beispiel dafür, wie seltsam Dinge werden können, ist, wie die verwalteten Sprachen am Ende von Prozessen mit der Finalisierung umgehen oder wie C # mit dem programmgesteuerten Anhalten von Anwendungsdomänen umgeht. Sie binden sich in Knoten, weil es Annahmen gibt, die in diesen extremen Fällen durch die Risse fallen.

3
Cort Ammon

Sprachen ignorieren, in denen Sie ohnehin keinen Speicher manuell freigeben ...

Was Sie momentan als "Programm" betrachten, kann irgendwann nur noch eine Funktion oder Methode sein, die Teil eines größeren Programms ist. Und dann wird diese Funktion möglicherweise mehrmals aufgerufen. Und dann ist Speicher, den Sie "manuell freigeben" sollten, ein Speicherverlust. Das ist natürlich ein Urteilsspruch.

1
gnasher729

Nein, es ist nicht notwendig, aber es ist eine gute Idee.

Sie sagen, Sie haben das Gefühl, dass "mysteriöse und schreckliche Dinge passieren würden, wenn ich nach der Verwendung des Speichers keinen Speicher mehr freigeben würde."

In technischer Hinsicht ist die einzige Folge davon, dass Ihr Programm so lange mehr Speicherplatz beansprucht, bis entweder ein festes Limit erreicht ist (z. B. Ihr virtueller Adressraum ist erschöpft) oder die Leistung nicht mehr akzeptabel ist. Wenn das Programm beendet werden soll, spielt dies keine Rolle, da der Prozess effektiv nicht mehr existiert. Bei den "mysteriösen und schrecklichen Dingen" geht es ausschließlich um den mentalen Zustand des Entwicklers. Das Finden der Quelle eines Speicherverlusts kann ein absoluter Albtraum sein (dies ist eine Untertreibung), und es erfordert viel Geschick und Disziplin, um Code zu schreiben, der leckfrei ist. Der empfohlene Ansatz zur Entwicklung dieser Fähigkeit und Disziplin besteht darin, immer Speicher freizugeben, wenn dieser nicht mehr benötigt wird, selbst 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 gesagt haben.

Allerdings, es gibt mindestens einen Fall, in dem es besser ist , nicht Speicher kurz vor Programmbeendigung freizugeben.

Stellen Sie sich den Fall vor, in dem Sie Millionen kleiner Zuweisungen vorgenommen haben und diese größtenteils auf die Festplatte übertragen wurden. Wenn Sie anfangen, alles freizugeben, muss der größte Teil Ihres Speichers wieder auf RAM) umgeschaltet werden, damit auf die Buchhaltungsinformationen zugegriffen werden kann, um die Daten sofort zu verwerfen Nur wenige Minuten, um zu beenden! Ganz zu schweigen davon, dass während dieser Zeit viel Druck auf die Festplatte und den physischen Speicher ausgeübt wird. Wenn der physische Speicher zunächst knapp ist (möglicherweise wird das Programm geschlossen, weil ein anderes Programm viel zerkaut Speicher), dann müssen einzelne Seiten möglicherweise mehrmals ein- und ausgelagert werden, wenn mehrere Objekte von derselben Seite freigegeben werden müssen, aber nicht nacheinander freigegeben werden.

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 Festplattenzugriff erforderlich ist.

Es ist wichtig zu beachten, dass in einer Sprache OO] durch das Aufrufen des Destruktors eines Objekts auch das Auslagern des Speichers erzwungen wird. Wenn Sie dies tun müssen, können Sie den Speicher auch freigeben.

1
Artelius

Da es sehr wahrscheinlich ist, dass Sie in einiger Zeit Ihr Programm ändern, es möglicherweise in etwas anderes integrieren, mehrere Instanzen nacheinander oder parallel ausführen möchten. Dann wird es notwendig, diesen Speicher manuell freizugeben - aber Sie werden sich nicht mehr an die Umstände erinnern und es wird Sie viel mehr Zeit kosten, 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.

1
Agent_L

Die Hauptgründe für die manuelle Bereinigung sind: Es ist weniger wahrscheinlich, dass Sie einen echten Speicherverlust mit einem Speicherblock verwechseln, der nur beim Beenden bereinigt wird. Manchmal werden Fehler im Allokator nur dann erkannt, wenn Sie die gesamte Datenstruktur durchlaufen Wenn Sie die Zuordnung aufheben, werden Sie viel weniger Kopfschmerzen haben, wenn Sie jemals eine Umgestaltung vornehmen, sodass ein Objekt tut freigegeben werden muss, bevor das Programm beendet wird.

Die Hauptgründe, warum Sie nicht manuell bereinigen sollten, sind: Leistung, Leistung, Leistung und die Möglichkeit eines Fehlers in Ihrem unnötigen Bereinigungscode, der sich in einen Absturzfehler oder eine Sicherheit 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 beizubehalten, damit Sie die Vorteile des Schreibens und Debuggens des Codes nutzen können, und dann, wenn Sie empirisch festgestellt haben, dass er zu viel ist Weisen Sie den Compiler an, ihn in Ihrer endgültigen ausführbaren Datei zu überspringen. Und eine Verzögerung beim Schließen des Programms ist fast per Definition kaum jemals im kritischen Pfad.

0
Davislor