Usuwanie wskaźnika w C ++

92

Kontekst: Próbuję zawinąć głowę wokół wskazówek, widzieliśmy je kilka tygodni temu w szkole i podczas dzisiejszych ćwiczeń wpadłem na głupiego? problem, może to być dla ciebie bardzo proste, ale mam niewielkie doświadczenie w programowaniu.

Widziałem w SO sporo pytań dotyczących usuwania wskaźników, ale wszystkie wydają się być związane z usunięciem klasy, a nie „prostym” wskaźnikiem (lub jakimkolwiek właściwym terminem), oto kod, który próbuję biegać:

#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.

}

Więc moje pytania to:

  1. Dlaczego pierwszy przypadek nie zadziała? Wydaje się, że użycie i usuwanie wskaźnika jest najprostsze? Błąd mówi, że pamięć nie została przydzielona, ​​ale „cout” zwrócił adres.
  2. W drugim przykładzie błąd nie jest wyzwalany, ale wykonanie cout wartości myPointer nadal zwraca adres pamięci?
  3. Czy numer 3 naprawdę działa? Wydaje mi się, że działa, wskaźnik nie przechowuje już adresu, czy to właściwy sposób na usunięcie wskaźnika?

Przepraszam za długie pytanie, chciałem wyjaśnić to tak jasno, jak to tylko możliwe, a także powtórzyć, mam niewielkie doświadczenie w programowaniu, więc jeśli ktoś mógłby odpowiedzieć na to używając terminów laika, byłoby to bardzo wdzięczne!

leopic
źródło
16
Powodem, dla którego nie widzisz pierwszego przykładu, jest to, że jest zły. Tylko deleteto, co ty new. Po usunięciu wskaźnika nie jest również wymagane, aby ustawiał się na NULL. Jeśli chcesz mieć tam bezpieczeństwo, użyj inteligentnych wskaźników, które zwalniają pamięć i wyświetlają błędy, gdy próbujesz uzyskać do nich dostęp, gdy czegoś nie zawierają.
chris
Hmm okej, nie jestem pewien, jakie są mądre wskazówki, ale przyjrzę się temu, dzięki!
leopic
1
Krótko mówiąc, robią to, co opisałem. Aby zatrzymać coś nowego, dzwonisz, reseta to uwalnia stare. Aby go zwolnić bez wymiany, dzwonisz release. Kiedy wychodzi poza zakres, jest niszczona i może zwolnić pamięć w zależności od jej typu. std::unique_ptrjest przeznaczony tylko dla jednego właściciela. std::shared_ptrzwalnia go, gdy ostatni właściciel przestaje być właścicielem zasobu. Są również wyjątkowo bezpieczne. Jeśli przydzielisz zasób za pomocą jednego, a następnie napotkasz wyjątek, zasób zostanie prawidłowo zwolniony.
chris

Odpowiedzi:

168

1 i 2

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

Pierwsza zmienna została przydzielona na stosie. Możesz wywołać usuwanie tylko w pamięci przydzielonej dynamicznie (na stercie) za pomocą newoperatora.

3.

  myPointer = NULL;
  delete myPointer;

Powyższe nic nie dało . Nic nie uwolniłeś, ponieważ wskaźnik wskazywał na NULL.


Nie należy robić następujących rzeczy:

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

Wskazałeś go na NULL, pozostawiając wyciek pamięci (nowy przydzielony int). Powinieneś uwolnić wspomnienie, na które wskazywałeś. Nie ma już możliwości uzyskania dostępu do tego przydzielonego new int, stąd wyciek pamięci.


Właściwy sposób:

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

Lepszy sposób:

Jeśli używasz C ++, nie używaj surowych wskaźników. Zamiast tego użyj inteligentnych wskaźników, które mogą obsłużyć te rzeczy przy niewielkim nakładzie pracy. C ++ 11 zawiera kilka .

Anirudh Ramanathan
źródło
13
<pedantry> „Na stosie” to szczegół implementacji - taki, o którym C ++ wyraźnie unika wspominania. Bardziej poprawnym terminem jest „z automatycznym okresem przechowywania”. (C ++ 11, 3.7.3) </
pedantry
4
Dziękuję, wybrałem Twoją odpowiedź na a) wyjaśnienie, co było nie tak i b) podanie najlepszych praktyk, wielkie dzięki!
leopic
6
@Tqn To nie jest w porządku. delete myPointerzwalnia *myPointer. To jest poprawne. Ale myPointernadal wskazuje miejsce w pamięci, które zostało zwolnione i nie powinno być używane, ponieważ jest to UB. Będzie niedostępna po zakończeniu zakresu tylko JEŚLI była to najpierw zmienna lokalna.
Anirudh Ramanathan,
2
@DarkCthulhu Thanks! (Dosłownie) uczę się czegoś newkażdego dnia. (Jestem tandetny!)
Tqn
1
@AmelSalibasic Pamięć skojarzona ze zmienną na stosie zostanie zwolniona tylko wtedy, gdy wyjdzie poza zakres. Przypisanie go do NULLzapobiega późniejszemu nadużywaniu go.
Anirudh Ramanathan,
24

Uważam, że nie w pełni rozumiesz, jak działają wskaźniki.
Kiedy masz wskaźnik wskazujący na jakąś pamięć, są trzy różne rzeczy, które musisz zrozumieć:
- istnieje „to, na co wskazuje” wskaźnik (pamięć)
- ten adres pamięci
- nie wszystkie wskaźniki muszą mieć skasowaną pamięć: ty tylko potrzeba usunięcia pamięci, która została przydzielona dynamicznie ( newoperator używany ).

Wyobrażać sobie:

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!

Kiedy to zrobiłeś

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.

C ++ pozwala na próbę deletewskazania wskaźnika, nullale tak naprawdę nic nie robi, po prostu nie daje żadnego błędu.

salgadokk
źródło
2
Dzięki, TO było bardzo pomocne, pomyślałem, że MUSZĘ usunąć wszystkie wskaźniki, nie wiedziałem, że dotyczy to tylko tych, które były nowe, dzięki.
leopic
13

Wskaźniki są podobne do zwykłych zmiennych, ponieważ nie trzeba ich usuwać. Są one usuwane z pamięci na końcu wykonywania funkcji i / lub na końcu programu.

Możesz jednak użyć wskaźników do przydzielenia `` bloku '' pamięci, na przykład w ten sposób:

int *some_integers = new int[20000]

Spowoduje to przydzielenie miejsca w pamięci na 20000 liczb całkowitych. Przydatne, ponieważ stos ma ograniczony rozmiar i możesz chcieć zadzierać z dużym ładowaniem „int” bez błędu przepełnienia stosu.

Za każdym razem, gdy wywołujesz new, powinieneś następnie „usunąć” na końcu programu, ponieważ w przeciwnym razie wystąpi wyciek pamięci, a część przydzielonej pamięci nigdy nie zostanie zwrócona do użycia przez inne programy. Aby to zrobić:

delete [] some_integers;

Mam nadzieję, że to pomoże.

user3728501
źródło
1
Chcę tylko dodać, że przydzielona pamięć ZOSTANIE zwrócona do użycia przez inne programy, ale tylko PO zakończeniu wykonywania programu.
sk4l
7

W C ++ istnieje zasada, że ​​każdy nowy jest usuwany .

  1. Dlaczego pierwszy przypadek nie zadziała? Wydaje się, że użycie i usuwanie wskaźnika jest najprostsze? Błąd mówi, że pamięć nie została przydzielona, ​​ale „cout” zwrócił adres.

nowy nigdy nie jest nazywany. Zatem adres, który drukuje cout jest adresem lokalizacji pamięci myVar lub wartością przypisaną do myPointer w tym przypadku. Przez pisanie:

myPointer = &myVar;

mówisz:

myPointer = Adres, pod którym przechowywane są dane w myVar

  1. W drugim przykładzie błąd nie jest wyzwalany, ale wykonanie cout wartości myPointer nadal zwraca adres pamięci?

Zwraca adres wskazujący lokalizację pamięci, która została usunięta. Ponieważ najpierw tworzysz wskaźnik i przypisujesz jego wartość do myPointer, po drugie go usuwasz, a po trzecie drukujesz. Jeśli więc nie przypiszesz innej wartości do myPointer, usunięty adres pozostanie.

  1. Czy numer 3 naprawdę działa? Wydaje mi się, że działa, wskaźnik nie przechowuje już adresu, czy to właściwy sposób na usunięcie wskaźnika?

NULL równa się 0, usuwasz 0, więc nic nie usuwasz. I logiczne jest, że wypisuje 0, ponieważ zrobiłeś:

myPointer = NULL;

co jest równe:

myPointer = 0;
fonZ
źródło
4
  1. Próbujesz usunąć zmienną przydzieloną na stosie. Nie możesz tego zrobić
  2. Usunięcie wskaźnika w rzeczywistości nie niszczy wskaźnika, tylko zajęta pamięć jest zwracana do systemu operacyjnego. Możesz uzyskać do niego dostęp, dopóki pamięć nie zostanie użyta dla innej zmiennej lub w inny sposób manipulowana. Dlatego dobrą praktyką jest ustawienie wskaźnika na NULL (0) po usunięciu.
  3. Usunięcie wskaźnika NULL niczego nie usuwa.
Hakan Serce
źródło
2
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
źródło
1
Ponadto zapoznaj się z tym wykładem na temat ramek stosu youtube.com/watch?v=bjObm0hxIYY i youtube.com/watch?v=Rxvv9krECNw na temat wskaźników.
Casper Beyer