Ich habe zuerst C # gelernt und jetzt beginne ich mit C ++. Soweit ich weiß, new
ähnelt der Operator in C ++ nicht dem in C #.
Können Sie den Grund für den Speicherverlust in diesem Beispielcode erklären?
class A { ... };
struct B { ... };
A *object1 = new A();
B object2 = *(new B());
Antworten:
Was ist los
Wenn Sie schreiben
T t;
, erstellen Sie ein Objekt vom TypT
mit automatischer Speicherdauer . Es wird automatisch bereinigt, wenn es außerhalb des Gültigkeitsbereichs liegt.Beim Schreiben
new T()
erstellen Sie ein Objekt vom TypT
mit dynamischer Speicherdauer . Es wird nicht automatisch aufgeräumt.Sie müssen einen Zeiger darauf übergeben,
delete
um es zu bereinigen:Ihr zweites Beispiel ist jedoch schlechter: Sie dereferenzieren den Zeiger und erstellen eine Kopie des Objekts. Auf diese Weise verlieren Sie den Zeiger auf das mit erstellte Objekt
new
, sodass Sie es niemals löschen können, selbst wenn Sie möchten!Was du machen solltest
Sie sollten die automatische Speicherdauer bevorzugen. Benötigen Sie ein neues Objekt, schreiben Sie einfach:
Wenn Sie eine dynamische Speicherdauer benötigen, speichern Sie den Zeiger auf das zugewiesene Objekt in einem Objekt mit automatischer Speicherdauer, das es automatisch löscht.
Dies ist eine gebräuchliche Redewendung, die unter dem nicht sehr beschreibenden Namen RAII ( Resource Acquisition Is Initialization ) geführt wird. Wenn Sie eine Ressource erwerben, die bereinigt werden muss, stecken Sie sie in ein Objekt mit automatischer Speicherdauer, damit Sie sich nicht um die Bereinigung kümmern müssen. Dies gilt für jede Ressource, sei es Speicher, geöffnete Dateien, Netzwerkverbindungen oder was auch immer Sie möchten.
Dieses
automatic_pointer
Ding existiert bereits in verschiedenen Formen, ich habe es nur bereitgestellt, um ein Beispiel zu geben. Eine sehr ähnliche Klasse existiert in der Standardbibliothek namensstd::unique_ptr
.Es gibt auch eine alte (vor C ++ 11) mit dem Namen
auto_ptr
, die jetzt jedoch veraltet ist, da sie ein seltsames Kopierverhalten aufweist.Und dann gibt es einige noch intelligentere Beispiele,
std::shared_ptr
die mehrere Zeiger auf dasselbe Objekt zulassen und es erst bereinigen, wenn der letzte Zeiger zerstört wird.quelle
*p += 2
wie mit einem normalen Zeiger tun können . Wenn es nicht als Referenz zurückgegeben würde, würde es das Verhalten eines normalen Zeigers nicht nachahmen, was hier beabsichtigt ist.Eine schrittweise Erklärung:
Am Ende haben Sie also ein Objekt auf dem Heap ohne Zeiger darauf, sodass es nicht gelöscht werden kann.
Das andere Beispiel:
ist nur dann ein Speicherverlust, wenn Sie
delete
den zugewiesenen Speicher vergessen haben :In C ++ gibt es Objekte mit automatischem Speicher, auf dem Stapel erstellte Objekte, die automatisch entsorgt werden, und Objekte mit dynamischem Speicher auf dem Heap, denen Sie zuordnen
new
und mit denen Sie sich befreien müssendelete
. (das ist alles grob ausgedrückt)Denken Sie, dass Sie
delete
für jedes Objekt eine zuweisen solltennew
.BEARBEITEN
Kommen Sie, um darüber nachzudenken,
object2
muss kein Speicherverlust sein.Der folgende Code dient nur dazu, einen Punkt zu verdeutlichen. Es ist eine schlechte Idee, Code wie diesen niemals zu mögen:
In diesem Fall ist es, da
other
es als Referenz übergeben wird, das genaue Objekt, auf das von gezeigt wirdnew B()
. Daher&other
würde das Abrufen der Adresse durch und das Löschen des Zeigers den Speicher freigeben.Aber ich kann das nicht genug betonen, tu das nicht. Es ist nur hier, um einen Punkt zu machen.
quelle
Gegeben zwei "Objekte":
Sie werden nicht denselben Speicherort belegen. Mit anderen Worten,
&a != &b
Wenn Sie den Wert des einen dem anderen zuweisen, ändert sich nicht der Speicherort, sondern der Inhalt:
Intuitiv funktionieren Zeiger- "Objekte" auf die gleiche Weise:
Schauen wir uns nun Ihr Beispiel an:
Dies weist den Wert von zu
new A()
zuobject1
. Der Wert ist ein Zeiger, was bedeutetobject1 == new A()
, aber&object1 != &(new A())
. (Beachten Sie, dass dieses Beispiel kein gültiger Code ist, sondern nur zur Erläuterung dient.)Da der Wert des Zeigers erhalten bleibt, können wir den Speicher freigeben, auf den er verweist:
delete object1;
Aufgrund unserer Regel verhält sich dies genauso wiedelete (new A());
bei keinem Leck.In Ihrem zweiten Beispiel kopieren Sie das Objekt, auf das Sie zeigen. Der Wert ist der Inhalt dieses Objekts, nicht der eigentliche Zeiger. Wie in jedem anderen Fall
&object2 != &*(new A())
.Wir haben den Zeiger auf den zugewiesenen Speicher verloren und können ihn daher nicht freigeben.
delete &object2;
mag scheinen, als würde es funktionieren, aber weil&object2 != &*(new A())
es nicht gleichwertigdelete (new A())
und so ungültig ist.quelle
In C # und Java verwenden Sie new, um eine Instanz einer Klasse zu erstellen, und müssen sich dann nicht mehr darum kümmern, sie später zu zerstören.
C ++ hat auch ein Schlüsselwort "new", das ein Objekt erstellt. Im Gegensatz zu Java oder C # ist dies jedoch nicht die einzige Möglichkeit, ein Objekt zu erstellen.
C ++ verfügt über zwei Mechanismen zum Erstellen eines Objekts:
Bei der automatischen Erstellung erstellen Sie das Objekt in einer Umgebung mit Gültigkeitsbereich: - in einer Funktion oder - als Mitglied einer Klasse (oder Struktur).
In einer Funktion würden Sie es folgendermaßen erstellen:
Innerhalb einer Klasse würden Sie es normalerweise so erstellen:
Im ersten Fall werden die Objekte beim Verlassen des Bereichsblocks automatisch zerstört. Dies kann eine Funktion oder ein Scope-Block innerhalb einer Funktion sein.
Im letzteren Fall wird das Objekt b zusammen mit der Instanz von A zerstört, in der es Mitglied ist.
Objekte werden mit new zugewiesen, wenn Sie die Lebensdauer des Objekts steuern müssen. Anschließend muss es gelöscht werden, um es zu zerstören. Mit der als RAII bekannten Technik kümmern Sie sich um das Löschen des Objekts an dem Punkt, an dem Sie es erstellen, indem Sie es in ein automatisches Objekt einfügen, und warten, bis der Destruktor des automatischen Objekts wirksam wird.
Ein solches Objekt ist ein shared_ptr, das eine "Löscher" -Logik aufruft, jedoch nur dann, wenn alle Instanzen des shared_ptr, die das Objekt gemeinsam nutzen, zerstört werden.
Während Ihr Code möglicherweise viele Aufrufe von new enthält, sollten Sie im Allgemeinen nur begrenzte Aufrufe zum Löschen haben und immer sicherstellen, dass diese von Destruktoren aufgerufen oder Objekte "gelöscht" werden, die in Smart-Pointer eingefügt werden.
Ihre Destruktoren sollten auch niemals Ausnahmen auslösen.
Wenn Sie dies tun, treten nur wenige Speicherlecks auf.
quelle
automatic
unddynamic
. Es gibt auchstatic
.Diese Leitung ist die Ursache für das Leck. Lassen Sie uns dies ein wenig auseinander nehmen ..
Objekt2 ist eine Variable vom Typ B, die beispielsweise an Adresse 1 gespeichert ist (Ja, ich wähle hier beliebige Zahlen aus). Auf der rechten Seite haben Sie nach einem neuen B oder einem Zeiger auf ein Objekt vom Typ B gefragt. Das Programm gibt Ihnen dies gerne und weist Ihr neues B der Adresse 2 zu und erstellt auch einen Zeiger in der Adresse 3. Nun Der einzige Weg, auf die Daten in Adresse 2 zuzugreifen, ist über den Zeiger in Adresse 3. Als nächstes haben Sie den Zeiger mit dereferenziert
*
, um die Daten auf die der Zeiger zeigt (die Daten in Adresse 2). Dadurch wird effektiv eine Kopie dieser Daten erstellt und Objekt2 zugewiesen, das in Adresse 1 zugewiesen ist. Denken Sie daran, dass es sich um eine KOPIE handelt, nicht um das Original.Hier ist das Problem:
Sie haben diesen Zeiger nie an einem Ort gespeichert, an dem Sie ihn verwenden können! Sobald diese Zuweisung abgeschlossen ist, ist der Zeiger (Speicher in Adresse3, über den Sie auf Adresse2 zugegriffen haben) außerhalb des Bereichs und außerhalb Ihrer Reichweite! Sie können delete nicht mehr aufrufen und daher den Speicher in Adresse2 nicht bereinigen. Was Ihnen bleibt, ist eine Kopie der Daten von Adresse2 in Adresse1. Zwei der gleichen Dinge, die in Erinnerung bleiben. Auf eines können Sie zugreifen, auf das andere nicht (weil Sie den Pfad dorthin verloren haben). Deshalb ist dies ein Speicherverlust.
Ich würde vorschlagen, dass Sie aus Ihrem C # -Hintergrund viel darüber lesen, wie Zeiger in C ++ funktionieren. Sie sind ein fortgeschrittenes Thema und können einige Zeit in Anspruch nehmen, aber ihre Verwendung wird für Sie von unschätzbarem Wert sein.
quelle
Wenn es einfacher ist, stellen Sie sich den Computerspeicher wie ein Hotel vor, und Programme sind Kunden, die Zimmer mieten, wenn sie diese benötigen.
Dieses Hotel funktioniert so, dass Sie ein Zimmer buchen und dem Portier mitteilen, wann Sie abreisen.
Wenn Sie Bücher buchen und einen Raum verlassen, ohne dies dem Portier mitzuteilen, wird der Portier denken, dass der Raum noch genutzt wird, und niemand anderem erlauben, ihn zu benutzen. In diesem Fall liegt ein Raumleck vor.
Wenn Ihr Programm Speicher zuweist und ihn nicht löscht (es verwendet ihn lediglich nicht mehr), glaubt der Computer, dass der Speicher noch verwendet wird, und lässt niemanden zu, ihn zu verwenden. Dies ist ein Speicherverlust.
Dies ist keine exakte Analogie, aber es könnte helfen.
quelle
Beim Erstellen erstellen
object2
Sie eine Kopie des Objekts, das Sie mit new erstellt haben, verlieren jedoch auch den (nie zugewiesenen) Zeiger (sodass Sie ihn später nicht mehr löschen können). Um dies zu vermeiden, müssten Sieobject2
eine Referenz erstellen.quelle
Nun, Sie erstellen einen Speicherverlust, wenn Sie den mit dem
new
Operator zugewiesenen Speicher nicht irgendwann freigeben, indem Sie dem Operator einen Zeiger auf diesen Speicher übergebendelete
.In Ihren beiden oben genannten Fällen:
Hier wird
delete
der Speicher nicht freigegeben. Wenn also Ihrobject1
Zeiger den Gültigkeitsbereich verlässt, tritt ein Speicherverlust auf, da Sie den Zeiger verloren haben und daher dendelete
Operator nicht verwenden können.Und hier
Sie verwerfen den von zurückgegebenen Zeiger
new B()
und können diesen Zeiger daher niemals an übergeben, damitdelete
der Speicher freigegeben wird. Daher ein weiterer Speicherverlust.quelle
Es ist diese Linie, die sofort leckt:
Hier erstellen Sie ein neues
B
Objekt auf dem Heap und anschließend eine Kopie auf dem Stapel. Auf diejenige, die auf dem Heap zugewiesen wurde, kann nicht mehr zugegriffen werden, und daher das Leck.Diese Leitung ist nicht sofort undicht:
Es wäre ein Leck sein , wenn Sie nie
delete
dobject1
though.quelle