Удаление указателя в 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. Во втором примере ошибка не запускается, но выполнение cout значения myPointer по- прежнему возвращает адрес памяти?
  3. №3 действительно работает? Мне кажется, работает, указатель больше не хранит адрес, это правильный способ удалить указатель?

Извините за длинный вопрос, хотел прояснить это как можно более ясно, также повторяю, у меня мало опыта программирования, поэтому, если бы кто-то мог ответить на этот вопрос, используя условия непрофессионала, был бы очень признателен!

леопик
источник
16
Причина, по которой вы не видите первый пример, в том, что он неверен. Только deleteто, что ты new. Также не требуется, чтобы указатель устанавливал значение NULL после его удаления. Если вам нужна безопасность, используйте интеллектуальные указатели, которые освобождают для вас память и выдают ошибки, когда вы пытаетесь получить к ним доступ, когда они чего-то не содержат.
Крис
Хм, ладно, я не уверен, что такое умные указатели, но я разберусь, спасибо!
leopic
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

Вы указали его на NULL, оставив утечку памяти (новый выделенный вами int). Вы должны освободить память, на которую указывали. Нет возможности получить доступ к выделенному new intбольше, отсюда утечка памяти.


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

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

Лучший способ:

Если вы используете C ++, не используйте необработанные указатели. Вместо этого используйте умные указатели, которые могут справиться с этим за вас с небольшими накладными расходами. В C ++ 11 есть несколько .

Анируд Раманатан
источник
13
<pedantry> «В стеке» - это деталь реализации, которую C ++ явно избегает упоминания. Более правильный термин - «с автоматическим сроком хранения». (C ++ 11, 3.7.3) </
pedantry
4
Спасибо, я выбрал ваш ответ для а) объяснения того, что было не так и б) предоставления лучших практик, большое спасибо!
leopic
6
@Tqn Это неправильно. delete myPointerосвобождает *myPointer. Это правильно. Но myPointerпродолжает указывать на ячейку памяти, которая была освобождена и не должна использоваться, поскольку это UB. Он будет недоступен после окончания области видимости только в том случае, если изначально это была локальная переменная.
Анируд Раманатан 08
2
@DarkCthulhu Спасибо! (Я буквально) учусь чему-то newкаждый день. (Я дрянной!)
Tqn 09
1
@AmelSalibasic Память, связанная с переменной в стеке, будет освобождена только после того, как она выйдет за пределы области видимости. Если присвоить ему значение, NULLмы не сможем использовать его позже.
Анируд Раманатан
24

Я считаю, что вы не совсем понимаете, как работают указатели.
Когда у вас есть указатель, указывающий на некоторую память, вы должны понимать три разные вещи:
- есть то, что указывает указатель (память)
- этот адрес памяти
- не все указатели нуждаются в удалении своей памяти: вы только необходимо удалить динамически выделенную память (используемый 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.

C ++ позволяет вам пытаться deleteуказать указатель, nullно на самом деле он ничего не делает, просто не дает никаких ошибок.

Салгадокк
источник
2
Спасибо, ЭТО было очень полезно, я думал, что должен был удалить все указатели, не знал, что это только для тех, которые были новыми, спасибо.
leopic
13

Указатели похожи на обычные переменные в том смысле, что их не нужно удалять. Они удаляются из памяти в конце выполнения функции и / или в конце программы.

Однако вы можете использовать указатели для выделения «блока» памяти, например, так:

int *some_integers = new int[20000]

Это выделит место в памяти для 20000 целых чисел. Полезно, потому что стек имеет ограниченный размер, и вы можете возиться с большой загрузкой целых чисел без ошибки переполнения стека.

Всякий раз, когда вы вызываете new, вы должны затем «удалить» в конце вашей программы, потому что в противном случае вы получите утечку памяти, и некоторое выделенное пространство памяти никогда не будет возвращено для использования другими программами. Сделать это:

delete [] some_integers;

Надеюсь, это поможет.

user3728501
источник
1
Я просто хочу добавить, что выделенная память будет возвращена для использования другими программами, но только ПОСЛЕ того, как ваша программа завершит выполнение.
sk4l
7

В C ++ есть правило, для каждого нового есть удаление .

  1. Почему не работает первое дело? Кажется, самый простой способ использования и удаления указателя? Ошибка говорит о том, что память не была выделена, но cout вернул адрес.

новый никогда не называется. Таким образом, адрес, который выводит cout, является адресом ячейки памяти myVar или значением, присвоенным myPointer в данном случае. Написав:

myPointer = &myVar;

ты говоришь:

myPointer = адрес, где хранятся данные в myVar

  1. Во втором примере ошибка не запускается, но выполнение cout значения myPointer по-прежнему возвращает адрес памяти?

Он возвращает адрес, указывающий на удаленную ячейку памяти. Потому что сначала вы создаете указатель и присваиваете его значение myPointer, затем удаляете его, а затем распечатываете. Поэтому, если вы не присвоите myPointer другое значение, удаленный адрес останется.

  1. №3 действительно работает? Мне кажется, работает, указатель больше не хранит адрес, это правильный способ удалить указатель?

NULL равно 0, вы удаляете 0, поэтому ничего не удаляете. И логично, что он печатает 0, потому что вы сделали:

myPointer = NULL;

что равно:

myPointer = 0;
fonZ
источник
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 об указателях.
Casper Beyer