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 malloc
sie 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 = 123
und 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, malloc
da 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?
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.Antworten:
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.
quelle
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!)
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.
quelle
=== 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.
quelle
malloc()
und beendet, wenn der Zeiger noch null ist, kann eine solche Funktion beliebig oft sicher verwendet werden, selbst wenn siefree
nie 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.null
dann vorhanden ist, wenn keine Zuordnung vorhanden ist, und nicht null, wenn eine Zuordnung vorhandennull
ist, 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.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!
quelle
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:
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.
quelle
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 seinfoo_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:
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:
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' :)
quelle
free() is cheap
Wenn 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.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.
quelle
free
zurexit
Zeit als schädlich angesehen.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.quelle
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.
quelle
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.
quelle
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:
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.
quelle
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.
quelle
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.
quelle
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.
quelle
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.
quelle
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.
quelle
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üfenmalloc()
und entsprechend zu handeln.quelle