Löschen eines Zeigers in C ++

91

Kontext: Ich versuche, meinen Kopf um Zeiger zu wickeln. Wir haben sie erst vor ein paar Wochen in der Schule gesehen und als ich heute übte, bin ich auf einen Dummkopf gestoßen. Problem, es kann für Sie sehr einfach sein, aber ich habe wenig bis gar keine Programmiererfahrung.

Ich habe in SO einige Fragen zum Löschen von Zeigern gesehen, aber alle scheinen mit dem Löschen einer Klasse und nicht mit einem 'einfachen' Zeiger (oder was auch immer der richtige Begriff sein mag) zusammenhängen. Hier ist der Code, den ich versuche Lauf:

#include <iostream>;

using namespace std;

int main() {
  int myVar,
      *myPointer;

  myVar = 8;
  myPointer = &myVar;

  cout << "delete-ing pointers " << endl;
  cout << "Memory address: " << myPointer << endl;

  // Seems I can't *just* delete it, as it triggers an error 
  delete myPointer;
  cout << "myPointer: " << myPointer << endl;
  // Error: a.out(14399) malloc: *** error for object 0x7fff61e537f4:
  // pointer being freed was not allocated
  // *** set a breakpoint in malloc_error_break to debug
  // Abort trap: 6

  // Using the new keyword befor deleting it works, but
  // does it really frees up the space? 
  myPointer = new int;
  delete myPointer;
  cout << "myPointer: " << myPointer << endl;
  // myPointer continues to store a memory address.

  // Using NULL before deleting it, seems to work. 
  myPointer = NULL;
  delete myPointer;
  cout << "myPointer: " << myPointer << endl;
  // myPointer returns 0.

}

Meine Fragen sind also:

  1. Warum funktioniert der erste Fall nicht? Scheint die einfachste Verwendung zu sein, um einen Zeiger zu verwenden und zu löschen? Der Fehler besagt, dass der Speicher nicht zugewiesen wurde, aber 'cout' eine Adresse zurückgegeben hat.
  2. Auf dem zweiten Beispiel wird der Fehler nicht ausgelöst wird , sondern ein cout des Wertes MyPointer tut noch eine Speicheradresse zurückgegeben.
  3. Funktioniert # 3 wirklich? Scheint mir zu funktionieren, der Zeiger speichert keine Adresse mehr. Ist dies der richtige Weg, um einen Zeiger zu löschen?

Entschuldigung für die lange Frage, wollte dies so klar wie möglich machen, auch um es noch einmal zu wiederholen, ich habe wenig Programmiererfahrung, also wenn jemand dies mit Laienbegriffen beantworten könnte, wäre es sehr dankbar!

leopisch
quelle
16
Der Grund, warum Sie das erste Beispiel nicht sehen, ist, dass es falsch ist. Nur deletewas du new. Es ist auch nicht erforderlich, dass sich der Zeiger nach dem Löschen auf NULL setzt. Wenn Sie dort Sicherheit wünschen, verwenden Sie intelligente Zeiger, die den Speicher für Sie freigeben und Fehler verursachen, wenn Sie versuchen, auf sie zuzugreifen, wenn sie nichts enthalten.
Chris
Hmm okay, ich bin mir nicht sicher, was intelligente Zeiger sind, aber ich werde mich darum kümmern, danke!
Leopic
1
Kurz gesagt, sie tun das, was ich beschrieben habe. Um etwas Neues zu halten, rufst du an resetund es befreit das Alte. Um es ersatzlos freizugeben, rufen Sie an release. Wenn es den Gültigkeitsbereich verlässt, wird es zerstört und kann den Speicher basierend auf dem Typ freigeben. std::unique_ptrist nur für einen Besitzer gedacht. std::shared_ptrgibt es frei, wenn der letzte Besitzer die Ressource nicht mehr besitzt. Sie sind auch ausnahmesicher. Wenn Sie einer Ressource eine zuweisen und dann auf eine Ausnahme stoßen, wird die Ressource ordnungsgemäß freigegeben.
Chris

Antworten:

164

1 & 2

myVar = 8; //not dynamically allocated. Can't call delete on it.
myPointer = new int; //dynamically allocated, can call delete on it.

Die erste Variable wurde auf dem Stapel zugewiesen. Sie können delete nur für den Speicher aufrufen, den Sie mithilfe des newOperators dynamisch zugewiesen haben (auf dem Heap) .

3.

  myPointer = NULL;
  delete myPointer;

Das Obige hat überhaupt nichts getan . Sie haben nichts befreit, da der Zeiger auf NULL zeigte.


Folgendes sollte nicht getan werden:

myPointer = new int;
myPointer = NULL; //leaked memory, no pointer to above int
delete myPointer; //no point at all

Sie haben es auf NULL gerichtet und dabei den durchgesickerten Speicher (den von Ihnen zugewiesenen neuen int) zurückgelassen. Sie sollten den Speicher freigeben, auf den Sie gezeigt haben. Es gibt keine Möglichkeit new intmehr, auf das zugewiesene zuzugreifen , daher Speicherverlust.


Der richtige Weg:

myPointer = new int;
delete myPointer; //freed memory
myPointer = NULL; //pointed dangling ptr to NULL

Der bessere Weg:

Wenn Sie C ++ verwenden, verwenden Sie keine Rohzeiger. Verwenden Sie stattdessen intelligente Zeiger, die diese Dinge mit geringem Aufwand für Sie erledigen können. C ++ 11 enthält mehrere .

Anirudh Ramanathan
quelle
13
<pedantry> "On the Stack" ist ein Implementierungsdetail, das in C ++ auffällig nicht erwähnt wird. Der korrektere Begriff lautet "mit automatischer Speicherdauer". (C ++ 11, 3.7.3) </
pedantry
4
Vielen Dank, ich habe Ihre Antwort ausgewählt, um a) zu erklären, was falsch war, und b) eine Best Practice zu geben, vielen Dank!
Leopic
6
@ Tqn Das ist nicht richtig. delete myPointerFreigabe *myPointer. Das ist richtig. Zeigt aber myPointerweiterhin auf einen Speicherort, der freigegeben wurde und nicht verwendet werden sollte, da es sich um UB handelt. Nach dem Ende des Gültigkeitsbereichs kann nur dann nicht mehr darauf zugegriffen werden, wenn es sich überhaupt um eine lokale Variable handelt.
Anirudh Ramanathan
2
@ DarkCthulhu Danke! (Ich wörtlich) lerne newjeden Tag etwas . (Ich bin kitschig!)
Tqn
1
@AmelSalibasic Der mit der Variablen auf dem Stapel verknüpfte Speicher wird erst freigegeben, wenn der Gültigkeitsbereich überschritten wird. Das Zuweisen zu NULLverhindert , dass wir es später missbrauchen.
Anirudh Ramanathan
23

Ich glaube, Sie verstehen nicht ganz, wie Zeiger funktionieren.
Wenn Sie einen Zeiger haben, der auf einen Speicher zeigt, müssen Sie drei verschiedene Dinge verstehen:
- Der Zeiger (der Speicher) zeigt auf "was zeigt"
- diese Speicheradresse
- nicht alle Zeiger müssen ihren Speicher löschen: nur Sie müssen Speicher löschen, der dynamisch zugewiesen wurde (verwendeter newOperator).

Vorstellen:

int *ptr = new int; 
// ptr has the address of the memory.
// at this point, the actual memory doesn't have anything.
*ptr = 8;
// you're assigning the integer 8 into that memory.
delete ptr;
// you are only deleting the memory.
// at this point the pointer still has the same memory address (as you could
//   notice from your 2nd test) but what inside that memory is gone!

Als du es getan hast

ptr = NULL;
// you didn't delete the memory
// you're only saying that this pointer is now pointing to "nowhere".
// the memory that was pointed by this pointer is now lost.

In C ++ können Sie versuchen, auf deleteeinen Zeiger zu verweisen, auf den verweist, der nulljedoch nichts bewirkt und nur keinen Fehler ausgibt.

salgadokk
quelle
2
Danke, das war super hilfreich, ich dachte, ich müsste alle Zeiger löschen, wusste nicht, dass das nur für diejenigen war, die neu waren, danke.
Leopic
13

Zeiger ähneln normalen Variablen, da Sie sie nicht löschen müssen. Sie werden am Ende einer Funktionsausführung und / oder am Ende des Programms aus dem Speicher entfernt.

Sie können jedoch Zeiger verwenden, um einen 'Speicherblock' zuzuweisen, zum Beispiel wie folgt:

int *some_integers = new int[20000]

Dadurch wird Speicherplatz für 20000 Ganzzahlen zugewiesen. Nützlich, da der Stapel eine begrenzte Größe hat und Sie möglicherweise mit einer großen Menge von "Ints" ohne einen Stapelüberlauffehler herumspielen möchten.

Wenn Sie new aufrufen, sollten Sie am Ende Ihres Programms "löschen", da sonst ein Speicherverlust auftritt und der zugewiesene Speicherplatz niemals für andere Programme zurückgegeben wird. Um dies zu tun:

delete [] some_integers;

Hoffentlich hilft das.

user3728501
quelle
1
Ich möchte nur hinzufügen, dass der zugewiesene Speicher für andere Programme zurückgegeben wird, aber erst, nachdem Ihr Programm die Ausführung beendet hat.
Sk4l
7

In C ++ gibt es eine Regel, für jede neue gibt es ein Löschen .

  1. Warum funktioniert der erste Fall nicht? Scheint die einfachste Verwendung zu sein, um einen Zeiger zu verwenden und zu löschen? Der Fehler besagt, dass der Speicher nicht zugewiesen wurde, aber 'cout' eine Adresse zurückgegeben hat.

neu wird nie genannt. Die Adresse, die cout druckt, ist also die Adresse des Speicherorts von myVar oder in diesem Fall der Wert, der myPointer zugewiesen wurde. Durch Schreiben:

myPointer = &myVar;

du sagst:

myPointer = Die Adresse, an der die Daten in myVar gespeichert sind

  1. Im zweiten Beispiel wird der Fehler nicht ausgelöst, aber wenn der Wert von myPointer überprüft wird, wird immer noch eine Speicheradresse zurückgegeben.

Es gibt eine Adresse zurück, die auf einen gelöschten Speicherort verweist. Weil Sie zuerst den Zeiger erstellen und myPointer seinen Wert zuweisen, zweitens ihn löschen und drittens drucken. Wenn Sie myPointer keinen anderen Wert zuweisen, bleibt die gelöschte Adresse erhalten.

  1. Funktioniert # 3 wirklich? Scheint mir zu funktionieren, der Zeiger speichert keine Adresse mehr. Ist dies der richtige Weg, um einen Zeiger zu löschen?

NULL ist gleich 0, Sie löschen 0, also löschen Sie nichts. Und es ist logisch, dass 0 ausgegeben wird, weil Sie Folgendes getan haben:

myPointer = NULL;

was gleich ist:

myPointer = 0;
fonZ
quelle
4
  1. Sie versuchen, eine auf dem Stapel zugewiesene Variable zu löschen. Du kannst das nicht machen
  2. Durch das Löschen eines Zeigers wird ein Zeiger nicht zerstört, sondern nur der belegte Speicher wird an das Betriebssystem zurückgegeben. Sie können darauf zugreifen, bis der Speicher für eine andere Variable verwendet oder anderweitig manipuliert wird. Es wird daher empfohlen, nach dem Löschen einen Zeiger auf NULL (0) zu setzen.
  3. Durch das Löschen eines NULL-Zeigers wird nichts gelöscht.
Hakan Serce
quelle
1
int value, *ptr;

value = 8;
ptr = &value;
// ptr points to value, which lives on a stack frame.
// you are not responsible for managing its lifetime.

ptr = new int;
delete ptr;
// yes this is the normal way to manage the lifetime of
// dynamically allocated memory, you new'ed it, you delete it.

ptr = nullptr;
delete ptr;
// this is illogical, essentially you are saying delete nothing.
Casper Beyer
quelle
1
Lesen Sie außerdem diese Vorlesung über Stapelrahmen youtube.com/watch?v=bjObm0hxIYY und youtube.com/watch?v=Rxvv9krECNw über Zeiger.
Casper Beyer