Java hat eine automatische GC, die hin und wieder die Welt stoppt, sich aber um den Müll auf einem Haufen kümmert. Jetzt haben C / C ++ - Anwendungen diese STW-Einfrierungen nicht mehr, und auch die Speichernutzung nimmt nicht unendlich zu. Wie wird dieses Verhalten erreicht? Wie werden die toten Gegenstände gepflegt?
c++
c
garbage-collection
Ju Shua
quelle
quelle
new
.What happens to garbage in C++?
Wird es normalerweise nicht in eine ausführbare Datei kompiliert?Antworten:
Der Programmierer ist dafür verantwortlich, dass Objekte, die er über erstellt hat, über
new
gelöscht werdendelete
. Wenn ein Objekt erstellt, aber nicht zerstört wird, bevor der letzte Zeiger oder Verweis auf dieses Objekt den Gültigkeitsbereich verlässt, fällt es durch die Risse und wird zu einem Speicherleck .Unglücklicherweise häufen sich C, C ++ und andere Sprachen, die keinen GC enthalten, mit der Zeit an. Dies kann dazu führen, dass einer Anwendung oder dem System der Speicher ausgeht und neue Speicherblöcke nicht zugeordnet werden können. Zu diesem Zeitpunkt muss der Benutzer auf das Beenden der Anwendung zurückgreifen, damit das Betriebssystem den verwendeten Speicher zurückfordern kann.
Um dieses Problem zu lindern, gibt es verschiedene Dinge, die einem Programmierer das Leben wesentlich erleichtern. Diese werden in erster Linie durch die Art des Geltungsbereichs gestützt .
Hier haben wir zwei Variablen erstellt. Sie sind im Blockbereich vorhanden , wie durch die
{}
geschweiften Klammern definiert. Wenn die Ausführung diesen Bereich verlässt, werden diese Objekte automatisch gelöscht. In diesem Fall istvariableThatIsAPointer
, wie der Name schon sagt, ein Zeiger auf ein Objekt im Speicher. Wenn der Gültigkeitsbereich überschritten wird, wird der Zeiger gelöscht, das Objekt, auf das er zeigt, bleibt jedoch erhalten. Hier haben wirdelete
dieses Objekt, bevor es aus dem Rahmen geht, um sicherzustellen, dass es keinen Speicherverlust gibt. Wir hätten diesen Zeiger jedoch auch an anderer Stelle übergeben und damit gerechnet, dass er später gelöscht wird.Diese Art des Geltungsbereichs erstreckt sich auf Klassen:
Hier gilt das gleiche Prinzip. Wir müssen uns keine Sorgen machen,
bar
wannFoo
gelöscht wird. Es wird jedochotherBar
nur der Zeiger gelöscht. WennotherBar
der einzige gültige Zeiger auf das Objekt ist, auf das er verweist, sollten wir ihn wahrscheinlichdelete
imFoo
Destruktor haben. Dies ist das Antriebskonzept von RAIIRAII ist auch die typische treibende Kraft hinter Smart Pointern . In dem C ++ Standard Library, diese sind
std::shared_ptr
,std::unique_ptr
undstd::weak_ptr
; obwohl ich andereshared_ptr
/weak_ptr
Implementierungen gesehen und verwendet habe , die den gleichen Konzepten folgen. Bei diesen verfolgt ein Referenzzähler, wie viele Zeiger auf ein bestimmtes Objekt vorhanden sind, unddelete
s das Objekt automatisch, wenn keine Referenzen mehr vorhanden sind.Darüber hinaus kommt es darauf an, dass ein Programmierer die richtigen Methoden und Disziplinen anwendet, um sicherzustellen, dass sein Code Objekte ordnungsgemäß verarbeitet.
quelle
delete
- das ist was ich gesucht habe. Genial.delete
wird von Smart Pointern automatisch aufgerufen, wenn Sie sie verwenden. Sie sollten sie daher jedes Mal verwenden, wenn ein automatischer Speicher nicht verwendet werden kann.delete
in Ihrem Anwendungscode enthalten sein müssen (und ab C ++ 14 dasselbe mitnew
), sondern stattdessen intelligente Zeiger und RAII verwenden müssen, um Heap-Objekte löschen zu lassen.std::unique_ptr
Typ undstd::make_unique
Funktion sind das direkte, einfachste Ersetzen vonnew
unddelete
auf Anwendungscodeebene.C ++ hat keine Garbage Collection.
C ++ - Anwendungen müssen ihren eigenen Müll entsorgen.
C ++ - Anwendungsprogrammierer müssen dies verstehen.
Wenn sie vergessen, wird das Ergebnis als "Speicherverlust" bezeichnet.
quelle
new
unddelete
.malloc
undfree
odernew[]
unddelete[]
oder irgendwelchen anderen Verteilern (wie Windows istGlobalAlloc
,LocalAlloc
,SHAlloc
,CoTaskMemAlloc
,VirtualAlloc
,HeapAlloc
, ...) und einen Speicher für Sie zugeordnet (zB überfopen
).In C, C ++ und anderen Systemen ohne Garbage Collector werden dem Entwickler durch die Sprache und ihre Bibliotheken Funktionen angeboten, die angeben, wann Speicher zurückgefordert werden kann.
Die grundlegendste Einrichtung ist die automatische Speicherung . Oft stellt die Sprache selbst sicher, dass Gegenstände entsorgt werden:
In diesem Fall muss der Compiler wissen, wann diese Werte nicht verwendet werden, und den damit verbundenen Speicherplatz zurückfordern.
Bei Verwendung des dynamischen Speichers wird in C Speicher traditionell mit zugewiesen
malloc
und mit zurückgefordertfree
. In C ++ wird Speicher traditionell mit reserviertnew
und mit zurückgefordertdelete
.C hat sich nicht viel über die Jahre verändert, aber moderne C ++ meidet
new
unddelete
vollständig und verlässt sich stattdessen auf Bibliothekseinrichtungen (die selbst nutzennew
unddelete
angemessen):std::unique_ptr
undstd::shared_ptr
std::string
,std::vector
,std::map
, ... alle intern verwalten dynamisch zugewiesenen Speicher transparentApropos
shared_ptr
, es besteht die Gefahr: Wenn ein Referenzzyklus gebildet und nicht unterbrochen wird, kann es zu einem Speicherverlust kommen. Es ist Sache des Entwicklers, diese Situation zu vermeiden, wobei die einfachste Möglichkeit darin besteht, sieshared_ptr
insgesamt zu vermeiden, und die zweitgrößte darin, Zyklen auf Typebene zu vermeiden.Als Ergebnis sind Speicherlecks kein Problem in C ++ verwenden, auch für neue Benutzer, solange sie verzichten
new
,delete
oderstd::shared_ptr
. Dies ist anders als bei C, wo eine strenge Disziplin erforderlich und im Allgemeinen unzureichend ist.Diese Antwort wäre jedoch nicht vollständig, ohne die Zwillingsschwester der Speicherlecks zu erwähnen: baumelnde Zeiger .
Ein baumelnder Zeiger (oder eine baumelnde Referenz) ist eine Gefahr, die dadurch entsteht, dass ein Zeiger oder eine Referenz auf ein Objekt, das tot ist, beibehalten wird. Zum Beispiel:
Die Verwendung eines baumelnden Zeigers oder einer Referenz ist undefiniertes Verhalten . Im Allgemeinen handelt es sich glücklicherweise um einen sofortigen Absturz. Leider führt dies häufig zu einer Speicherbeschädigung ... und von Zeit zu Zeit tritt seltsames Verhalten auf, weil der Compiler wirklich seltsamen Code ausgibt.
Undefiniertes Verhalten ist bis heute das größte Problem bei C und C ++ im Hinblick auf die Sicherheit / Korrektheit von Programmen. Vielleicht möchten Sie Rust nach einer Sprache ohne Garbage Collector und ohne undefiniertes Verhalten durchsuchen.
quelle
new
,delete
undshared_ptr
"; ohnenew
undshared_ptr
du hast direkt besitz also keine lecks. Natürlich haben Sie wahrscheinlich baumelnde Zeiger usw., aber ich fürchte, Sie müssen C ++ verlassen, um diese loszuwerden.C ++ hat dieses Ding namens RAII . Im Grunde bedeutet dies, dass der Müll beim Gehen aufgeräumt wird, anstatt ihn auf einem Stapel liegen zu lassen und den Reiniger nach Ihnen aufräumen zu lassen. (Stellen Sie sich vor, ich sehe in meinem Zimmer Fußball, während ich Bierdosen trinke und neue brauche. Der C ++ - Weg ist, die leere Dose auf dem Weg zum Kühlschrank zum Mülleimer zu bringen. Der C # -Weg ist, sie auf den Boden zu werfen und warten Sie, bis die Magd sie abgeholt hat, wenn sie zum Putzen kommt).
Jetzt ist es möglich, Speicher in C ++ zu verlieren, aber dazu müssen Sie die üblichen Konstrukte verlassen und zur C-Methode zurückkehren - einen Speicherblock zuweisen und verfolgen, wo sich dieser Block ohne Sprachunterstützung befindet. Einige Leute vergessen diesen Zeiger und können den Block nicht entfernen.
quelle
Es sollte beachtet werden, dass es im Fall von C ++ ein weit verbreitetes Missverständnis ist, dass "Sie manuelle Speicherverwaltung durchführen müssen". Tatsächlich führen Sie normalerweise keine Speicherverwaltung in Ihrem Code durch.
Objekte mit fester Größe (mit Gültigkeitsdauer des Bereichs)
In den allermeisten Fällen, in denen Sie ein Objekt benötigen, hat das Objekt eine definierte Lebensdauer in Ihrem Programm und wird auf dem Stapel erstellt. Dies funktioniert für alle integrierten primitiven Datentypen, aber auch für Instanzen von Klassen und Strukturen:
Stapelobjekte werden nach Beendigung der Funktion automatisch entfernt. In Java werden Objekte immer auf dem Heap erstellt und müssen daher durch einen Mechanismus wie Garbage Collection entfernt werden. Dies ist für Stapelobjekte kein Problem.
Objekte, die dynamische Daten verwalten (mit Gültigkeitsdauer des Bereichs)
Die Verwendung von Speicherplatz auf dem Stapel funktioniert für Objekte fester Größe. Wenn Sie eine variable Menge an Speicherplatz benötigen, z. B. ein Array, wird ein anderer Ansatz verwendet: Die Liste ist in einem Objekt fester Größe gekapselt, das den dynamischen Speicher für Sie verwaltet. Dies funktioniert, weil Objekte eine spezielle Bereinigungsfunktion haben können, den Destruktor. Es wird garantiert aufgerufen, wenn das Objekt den Gültigkeitsbereich verlässt und das Gegenteil des Konstruktors bewirkt:
In dem Code, in dem der Speicher verwendet wird, gibt es überhaupt keine Speicherverwaltung. Das einzige, was wir sicherstellen müssen, ist, dass das Objekt, das wir geschrieben haben, einen geeigneten Destruktor hat. Egal, wie wir den Gültigkeitsbereich verlassen
listTest
, sei es über eine Ausnahme oder einfach durch Zurückkehren, der Destruktor~MyList()
wird aufgerufen, und wir müssen keinen Speicher verwalten.(Ich halte es für eine witzige Designentscheidung, den binären NOT- Operator zu verwenden,
~
um den Destruktor anzugeben. Bei Verwendung mit Zahlen werden die Bits invertiert. In Analogie dazu wird hier angegeben, dass der Konstruktor invertiert wurde.)Grundsätzlich verwenden alle C ++ - Objekte, die dynamischen Speicher benötigen, diese Kapselung. Es wurde RAII ("Ressourcenbeschaffung ist Initialisierung") genannt, was eine ziemlich seltsame Art ist, die einfache Idee auszudrücken, dass sich Objekte um ihren eigenen Inhalt kümmern. was sie erwerben, ist ihr, um aufzuräumen.
Polymorphe Objekte und eine Lebensdauer, die den Rahmen sprengt
Beide Fälle betrafen Speicher mit einer klar definierten Lebensdauer: Die Lebensdauer entspricht dem Gültigkeitsbereich. Wenn wir nicht möchten, dass ein Objekt verfällt, wenn wir den Gültigkeitsbereich verlassen, gibt es einen dritten Mechanismus, der den Speicher für uns verwalten kann: einen intelligenten Zeiger. Intelligente Zeiger werden auch verwendet, wenn Sie Instanzen von Objekten haben, deren Typ zur Laufzeit variiert, die jedoch eine gemeinsame Schnittstelle oder Basisklasse haben:
Es gibt eine andere Art von intelligentem Zeiger,
std::shared_ptr
mit dem Objekte von mehreren Clients gemeinsam genutzt werden können. Sie löschen ihr enthaltenes Objekt nur, wenn der letzte Client den Gültigkeitsbereich verlässt, sodass sie in Situationen verwendet werden können, in denen völlig unbekannt ist, wie viele Clients vorhanden sind und wie lange sie das Objekt verwenden werden.Zusammenfassend stellen wir fest, dass Sie keine manuelle Speicherverwaltung durchführen. Alles ist gekapselt und wird dann durch eine vollautomatische, bereichsbezogene Speicherverwaltung erledigt. In den Fällen, in denen dies nicht ausreicht, werden intelligente Zeiger verwendet, die den Rohspeicher einkapseln.
Es wird als äußerst schlechte Praxis angesehen, Rohzeiger als Ressourcenbesitzer im gesamten C ++ - Code, Rohzuweisungen außerhalb von Konstruktoren und Rohaufrufe
delete
außerhalb von Destruktoren zu verwenden, da sie im Ausnahmefall kaum zu verwalten sind und im Allgemeinen nur schwer sicher zu verwenden sind.Das Beste: Dies funktioniert für alle Arten von Ressourcen
Einer der größten Vorteile von RAII ist, dass es nicht nur auf das Gedächtnis beschränkt ist. Tatsächlich bietet es eine sehr natürliche Möglichkeit, Ressourcen wie Dateien und Sockets (Öffnen / Schließen) und Synchronisationsmechanismen wie Mutexe (Sperren / Entsperren) zu verwalten. Grundsätzlich wird jede Ressource, die erworben werden kann und freigegeben werden muss, in C ++ genauso verwaltet, und nichts von dieser Verwaltung bleibt dem Benutzer überlassen. Es ist alles in Klassen gekapselt, die im Konstruktor akquirieren und im Destruktor freigeben.
Zum Beispiel wird eine Funktion, die einen Mutex sperrt, normalerweise so in C ++ geschrieben:
Andere Sprachen erschweren dies erheblich, indem Sie dies entweder manuell ausführen müssen (z. B. in einer
finally
Klausel) oder spezialisierte Mechanismen erzeugen, die dieses Problem lösen, jedoch nicht auf besonders elegante Weise (in der Regel später in ihrem Leben, wenn genügend Menschen vorhanden sind) litt unter dem Mangel). Solche Mechanismen sind Try-with-Resources in Java und die using- Anweisung in C #, die beide Annäherungen an C ++ 's RAII sind.Zusammenfassend war dies alles eine sehr oberflächliche Darstellung von RAII in C ++, aber ich hoffe, dass es den Lesern hilft, zu verstehen, dass das Speichern und sogar das Ressourcenmanagement in C ++ normalerweise nicht "manuell", sondern hauptsächlich automatisch ist.
quelle
delete
und du bist tot" Antworten, die über 30 Punkte schießen und akzeptiert werden, während diese fünf hat. Verwendet hier tatsächlich jemand C ++?In Bezug auf C bietet die Sprache keine Tools zum Verwalten des dynamisch zugewiesenen Speichers. Sie sind absolut dafür verantwortlich, dass jeder irgendwo
*alloc
eine Entsprechung hatfree
.Wirklich schlimm wird es, wenn eine Ressourcenzuweisung auf halbem Weg fehlschlägt. Versuchen Sie es erneut, führen Sie einen Rollback durch und beginnen Sie von vorne. Führen Sie einen Rollback durch und beenden Sie das Programm mit einem Fehler.
Hier ist beispielsweise eine Funktion zum Zuweisen eines nicht zusammenhängenden 2D-Arrays. Das Verhalten hier ist, dass, wenn ein Zuordnungsfehler in der Mitte des Prozesses auftritt, wir alles zurücksetzen und eine Fehleranzeige mit einem NULL-Zeiger zurückgeben:
Dieser Code ist stumpf hässlich mit jenen
goto
s, aber in Abwesenheit jede Art einer strukturierten Ausnahmebehandlung, das so ziemlich der einzige Weg ist , völlig ohne nur die Rettung mit dem Problem zu befassen, vor allem , wenn Ihr Code Ressourcenzuweisung ist verschachtelt mehr als eine Schleife tief. Dies ist eines der wenigen Male, bei denengoto
es sich tatsächlich um eine attraktive Option handelt. Andernfalls verwenden Sie eine Reihe von Flags und zusätzlichenif
Anweisungen.Sie können sich das Leben erleichtern, indem Sie für jede Ressource dedizierte Allokator- / Freigabefunktionen schreiben, wie z
quelle
goto
Aussagen. Dies wird in einigen Bereichen empfohlen. Es ist ein weit verbreitetes Schema zum Schutz gegen das Äquivalent von Ausnahmen in C. Schauen Sie sich den Linux-Kernel-Code an, der voll vongoto
Anweisungen ist - und der nicht leckt.goto
ist irrelevant. Es wäre besser lesbar, wenn Siegoto done;
zureturn arr;
undarr=NULL;done:return arr;
zu wechseln würdenreturn NULL;
. Obwohl es in komplizierteren Fällen tatsächlich mehreregoto
s geben kann, beginnt das Abwickeln mit unterschiedlichen Bereitschaftsstufen (was durch das Abwickeln des Ausnahmestapels in C ++ geschehen würde).Ich habe gelernt, Speicherprobleme in verschiedene Kategorien einzuteilen.
Einmal tropft. Angenommen, ein Programm verliert 100 Bytes zum Startzeitpunkt, nur um nie wieder zu verlieren. Das Aufspüren und Beseitigen dieser einmaligen Lecks ist nett (ich habe gerne einen sauberen Bericht mit einer Leckerkennungsfunktion), aber nicht unbedingt erforderlich. Manchmal gibt es größere Probleme, die angegriffen werden müssen.
Wiederholte Undichtigkeiten. Eine Funktion, die im Laufe einer Programmlebensdauer wiederholt aufgerufen wird und bei der regelmäßig Speicherplatz verloren geht, ist ein großes Problem. Diese Tropfen werden das Programm und möglicherweise das Betriebssystem zu Tode quälen.
Gegenseitige Referenzen. Wenn die Objekte A und B über gemeinsame Zeiger aufeinander verweisen, müssen Sie etwas Besonderes tun, entweder im Design dieser Klassen oder im Code, der diese Klassen implementiert / verwendet, um die Zirkularität zu durchbrechen. (Dies ist kein Problem für Sprachen, die mit Datenmüll gesammelt wurden.)
Sich zu sehr erinnern. Dies ist der böse Cousin von Müll / Speicherlecks. RAII wird hier nicht helfen, noch wird Müllabfuhr. Dies ist in jeder Sprache ein Problem. Wenn eine aktive Variable einen Pfad hat, der sie mit einem zufälligen Speicherblock verbindet, ist dieser zufällige Speicherblock kein Müll. Es ist schwierig, ein Programm vergesslich zu machen, damit es mehrere Tage laufen kann. Es ist sehr, sehr schwierig, ein Programm zu erstellen, das mehrere Monate laufen kann (z. B. bis die Festplatte ausfällt).
Ich habe lange, lange kein ernstes Problem mit Undichtigkeiten gehabt. Die Verwendung von RAII in C ++ hilft sehr dabei, diese Tropfen und Undichtigkeiten zu beseitigen. (Mit gemeinsam genutzten Zeigern muss man jedoch vorsichtig sein.) Viel wichtiger ist, dass ich Probleme mit Anwendungen hatte, deren Speichernutzung immer größer und größer wird, da nicht mehr benötigte Verbindungen zum Arbeitsspeicher bestehen.
quelle
Es ist Sache des C ++ - Programmierers, bei Bedarf seine eigene Form der Garbage Collection zu implementieren. Andernfalls tritt ein sogenannter "Memory Leak" auf. Es ist durchaus üblich, dass 'High-Level'-Sprachen (wie Java) eine Garbage Collection eingebaut haben,' Low-Level'-Sprachen wie C und C ++ jedoch nicht.
quelle