Видалення вказівника в C ++


92

Контекст: Я намагаюся обернути голову навколо покажчиків, ми щойно бачили їх пару тижнів тому в школі, і сьогодні, практикуючись, я натрапив на дурного? Проблема може бути для вас надзвичайно простою, але я майже не маю досвіду програмування.

У SO я бачив чимало запитань щодо видалення покажчиків, але всі вони, здається, пов’язані з видаленням класу, а не „простим” вказівником (або яким би там не був правильним терміном), ось код, який я намагаюся запустити:

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

}

Тож мої запитання:

  1. Чому перший випадок не спрацює? Здається найпростішим використанням і видаленням вказівника? Помилка повідомляє, що пам'ять не була виділена, але "cout" повернув адресу.
  2. У другому прикладі помилка не спрацьовує, але виконання мотивування значення myPointer все одно повертає адресу пам'яті?
  3. Чи справді №3 працює? Мені здається, це працює, вказівник більше не зберігає адресу, це правильний спосіб видалення вказівника?

Вибачте за довге запитання, хотів зробити це якомога чіткішим, а також повторити, я маю невеликий досвід програмування, тому, якби хтось міг відповісти на це, використовуючи непрофесійні умови, це було б дуже вдячне!


16
Причина, по якій ви не бачите першого прикладу, полягає в тому, що він помилковий. Тільки deleteте, що ти new. Також не потрібно, щоб вказівник встановлював значення NULL після його видалення. Якщо ви хочете там безпеки, використовуйте розумні вказівники, які звільняють пам’ять для вас і видають помилки при спробі отримати до них доступ, коли вони чогось не містять.
chris

Хм, добре, я не впевнений, що таке розумні вказівники, але я розгляну це, дякую!
леопатичний

1
У двох словах, вони роблять те, що я описав. Щоб утримати щось нове, ви дзвоните, resetі це звільняє старе. Щоб звільнити його без заміни, ви телефонуєте release. Коли він виходить за межі сфери дії, він руйнується і може звільнити пам’ять залежно від типу. std::unique_ptrпризначений лише для одного власника. std::shared_ptrзвільняє, коли останній власник перестає володіти ресурсом. Вони також є винятковими. Якщо ви виділите ресурс із одним, а потім зіткнетеся з винятком, ресурс буде належним чином звільнений.
Кріс

Відповіді:


168

1 і 2

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

Перша змінна була виділена в стек. Ви можете викликати видалення лише з пам'яті, яку ви динамічно виділили (у купі), використовуючи newоператор.

3.

  myPointer = NULL;
  delete myPointer;

Вищезазначене взагалі нічого не зробило . Ви нічого не звільнили, як вказівник вказував на NULL.


Не слід робити наступне:

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

Ви вказали на НУЛЬ, залишивши за собою витік пам'яті (новий int, який Ви виділили). Ви повинні звільнити пам’ять, на яку вказували. Більше неможливо отримати доступ до виділеного new int, отже, витік пам'яті.


Правильний спосіб:

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

Кращий спосіб:

Якщо ви використовуєте С ++, не використовуйте необроблені вказівники. Замість цього використовуйте розумні покажчики, які можуть впоратись із цими речами, мало накладаючи витрати. C ++ 11 поставляється з декількома .


13
<pedantry> "У стеку" - це деталь реалізації - та, яку C ++ помітно уникає згадувати. Більш правильний термін - "з автоматичною тривалістю зберігання". (C ++ 11, 3.7.3) </
pedantry

4
Дякую, я вибрав вашу відповідь для: а) пояснення помилки та б) надання найкращих практик, велике спасибі!
леопатичний

6
@Tqn Це неправильно. delete myPointerзвільняється *myPointer. Це правильно. Але myPointerпродовжує вказувати на місце пам'яті, яке було звільнене і не повинно використовуватися, оскільки це UB. Він буде недоступний після закінчення області дії, ЯКЩО спочатку це була локальна змінна.
Anirudh Ramanathan

2
@DarkCthulhu Дякую! (Я буквально) що-небудь чомусь вчуся new. (Я сирний!)
Tqn

1
@AmelSalibasic Пам'ять, пов'язана зі змінною у стеку, буде звільнена лише після того, як вона вийде за межі обсягу. Призначення цього NULLзапобігає зловживанню ним пізніше.
Anirudh Ramanathan

23

Я вважаю, ви не до кінця розумієте, як працюють покажчики.
Коли у вас є вказівник, який вказує на деяку пам’ять, ви повинні розуміти три різні речі:
- є те, на що вказує вказівник (пам’ять)
- ця адреса пам’яті
- не всім вказівникам потрібно видаляти пам’ять: ви потрібно видалити пам’ять, яка була динамічно розподілена (використовується newоператор).

Уявіть:

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!

Коли ти це зробив

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.

С ++ дозволяє вам спробувати deleteвказівник, який вказує, nullале насправді він нічого не робить, просто не видає жодної помилки.


2
Дякую, ЦЕ було дуже корисно, я думав, що МЕНЕ ВИДАЛИ видалити всі вказівники, не знав, що це лише для тих, хто був new'd, дякую.
леопатичний

13

Покажчики схожі на звичайні змінні тим, що їх не потрібно видаляти. Вони видаляються з пам'яті в кінці виконання функції та / або в кінці програми.

Однак ви можете використовувати покажчики для виділення "блоку" пам'яті, наприклад, наприклад:

int *some_integers = new int[20000]

Це виділить пам’ять для 20000 цілих чисел. Корисно, оскільки стек має обмежений розмір, і ви можете захотіти возитися з великим навантаженням "ints" без помилки переповнення стека.

Кожного разу, коли ви телефонуєте за новою, вам слід «видалити» в кінці вашої програми, оскільки в іншому випадку ви отримаєте витік пам’яті, а частина виділеного місця в пам’яті ніколи не повернеться для використання іншими програмами. Зробити це:

delete [] some_integers;

Сподіваюся, що це допомагає.


1
Я просто хочу додати, що виділена пам'ять буде повернута для використання іншими програмами, але тільки ПІСЛЯ завершення роботи вашої програми.
sk4l

7

У C ++ є правило, для кожного нового є видалення .

  1. Чому перший випадок не спрацює? Здається найпростішим використанням і видаленням вказівника? Помилка повідомляє, що пам'ять не була виділена, але "cout" повернув адресу.

нове ніколи не називається. Отже, адреса, яку друкує cout, є адресою розташування пам'яті myVar або значенням, призначеним myPointer в цьому випадку. Написавши:

myPointer = &myVar;

ти кажеш:

myPointer = Адреса місця, де зберігаються дані в myVar

  1. У другому прикладі помилка не спрацьовує, але виконання мотивування значення myPointer все одно повертає адресу пам'яті?

Він повертає адресу, яка вказує на місце пам'яті, яке було видалено. Оскільки спочатку ви створюєте покажчик і присвоюєте його значення myPointer, по-друге видаляєте його, по-третє, друкуєте. Отже, якщо ви не призначите інше значення myPointer, видалена адреса залишиться.

  1. Чи справді №3 працює? Мені здається, це працює, вказівник більше не зберігає адресу, це правильний спосіб видалення вказівника?

NULL дорівнює 0, ви видаляєте 0, тому ви нічого не видаляєте. І логічно, що він друкує 0, тому що ви зробили:

myPointer = NULL;

що дорівнює:

myPointer = 0;

4
  1. Ви намагаєтесь видалити змінну, виділену в стеку. Ви не можете цього зробити
  2. Видалення вказівника насправді не руйнує вказівник, просто зайнята пам'ять віддається ОС. Ви можете отримати до нього доступ, доки пам’ять не буде використана для іншої змінної або іншим чином не оброблена. Тож є гарною практикою встановлювати покажчик на NULL (0) після видалення.
  3. Видалення покажчика NULL нічого не видаляє.

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.

1
Крім того, перегляньте цю лекцію про фрейми стеків youtube.com/watch?v=bjObm0hxIYY та youtube.com/watch?v=Rxvv9krECNw на покажчиках.
Каспер Бейер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.