2016-11-27 1 views
3

Предположим, у меня есть динамическое выделение памяти через p1 следующим образом,Что касается динамического распределения памяти в C++

int* p1 = new int; 
*p1 = 1; 

Я знаю, что память ссылается p1 может быть освобожден с помощью

delete p1; 
p1 = nullptr; 

Но я интересно, есть ли другой указатель p2, указывающий на 1, могу ли я delete этот указатель, чтобы освободить память? И что будет с указателем p1? Кроме того, какова связь между p1 и p2 по существу? Например,

int* p1 = new int; 
*p1 = 1; 
int* p2 = p1; 
// Can I delete p2 like this? And what would happen to p1? 
delete p2; 
p2 = nullptr; 
+3

*** И что произойдет с p1? *** Неопределенное поведение, если вы попытаетесь разыменовать p1. – drescherjm

ответ

8

Вы можете удалить p2, но разыменования p1 приведет к неопределенному поведению, а также возможные ошибки сегментации.

Это работает так:

  1. Память выделяется в какой-то адрес.
  2. Оба p1 и p2 указывают на это место памяти.
  3. Один раз p2 удален - p1 все еще указывает на это место памяти. Нет утечки, и все в порядке - просто не разыскивайте p1. Вы можете свободно делать p1 = nullptr, но не можете *p1 = 1. Кроме того, вы не можете удалить p1, так как он уже удален, и вы, вероятно, поймаете segfault.
2

Вы можете удалить p1 или p2. Не будет никакой разницы. Но вы не должны удалить оба. Плюс после того, как вы удалили один, вы не должны использовать другой. За это отвечает программист. Сам язык не даст никакой помощи. Существует множество различных способов написания кода.

Существует несколько методов/шаблонов для обработки этого. Очень часто для этого используются интеллектуальные указатели. Посмотрите на документацию std::shared_ptr. Не используйте устаревшие auto_ptr.

Мой любимый паттерн - «собственность». Это означает, что один указатель «владеет» распределением, а все остальные просто используют. Это требует определенной дисциплины во время программирования, но как только это усилие применяется, полученный код ясен и прост. Например:

class MyClass 
{ 
public: ~MyClass() { for(char *p: myStringsDict) delete p; } 

private: 
    std::unordered_set<char*> myStringsDict; 
}; 

Глядя на этот класс ясно (хотя было бы неплохо добавить правильный комментарий), что он владеет словарем строк, и эти строки действительны до тех пор, пока существует экземпляр этого класса , Эти указатели могут использоваться в структурах, принадлежащих этому классу, их можно передавать как параметры для функций и т. Д. Ясно, что их больше не следует использовать.

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

Действительно хорошее решение для всех этих проблем - сбор мусора. Когда используются явные распределения, программисту необходимо применить дополнительные усилия тем или иным способом.

+0

Очень просветительская. Интересно, для вас любимая «собственность», как вы ее точно реализуете? Здесь достаточно просто продемонстрировать на примере моей игрушки? – Nicholas

2

И что будет с указателем p1? Кроме того, какова взаимосвязь между p1 и p2?

Их существенное отношение состоит в том, что они указывают на тот же адрес, полученный из динамического распределения памяти после назначения int* p2 = p1;.

Таким образом, удаление любого из них освободит выделенную память. Установка одного из них на nullptr не повлияет на другое.

Итак, вы остаетесь с оборванным указателем, который нельзя удалить безопасно.

4

Вы описываете очень известную проблему в (старом) C++: когда несколько указателей указывают на одну и ту же динамическую память, какая из них ее удаляет?

если вы удалите как p1 и p2 вы дважды удалить память, которые имеют неопределенное поведение (аварии, в лучшем случае), если вы удалите p1 или p2 и вы продолжать использовать память с помощью другого указателя - вы используя висячий указатель, который является неопределенным поведением (авария, в лучшем случае). вам нужно убедиться, что при удалении одного указателя вы не должны использовать эту память в других указателях.

C++ 11 введен стандартный способ решения этой проблемы: с помощью указателя самостоятельного подсчета, который только последний указатель удаляет память:

auto p1 = std::make_shared<int>(0); 
auto p2 = p1; 

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

+1

Если у вас нет циклической структуры данных, в этом случае вам придется беспокоиться, кто еще что-то удаляет. –

+0

@ н.м. конечно, но я не собираюсь вводить весь предмет умных указателей в один ответ. есть и другие соображения: например, дебютный костюм. –

1

Давайте рассмотрим аналогию с недвижимостью, где память играет роль земли и указателей, не так удивительно, что они выступают в качестве адресов.

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

int *p = new int; 

Вы просите город найти небольшой неиспользованный участок земли и назначьте себе титул. Вы записываете свой адрес на желтой ноте.

*p = 1; 

Вы строите аккуратный домик по этому адресу.

int *q = p; 

Вы делаете копию желтой записки. Вы забыли об этом в течение некоторого времени.

delete p; 

Вы разрушаете здание и отказываетесь от своих прав на участок земли. Город может выделить его кому-то другому. Возможно, кто-то хочет построить там еще одно небольшое здание или, может быть, заложить железнодорожные пути или создать бассейн акул. Обратите внимание: это ничего не делает для любой из ваших желтых заметок.

p = nullptr; 

Вы протираете желтую записку чистой. Ваша другая желтая нота задерживается.

*q = 2; 

Вы найдете другую желтую ноту, прочитайте у нее отрывочный адрес и предположите, что земля принадлежит вам. Плохой ход. Вы начинаете строить аккуратный домик на чужой земле. Новые владельцы не могли заботиться (они не знают). Завтра они могут разрушить ваше здание и положить их на место, или захватить вас поездом, или, возможно, сбросить 100000 тонн воды и 3 makos на вас. Это довольно неприятно! Не трогайте то, что не принадлежит вам.

0

при распределении динамической памяти с использованием нового он должен быть освобожден удалением, если вы создаете p1, используя новый, затем освободите его, используя delete. вы объявляете p2 в качестве указателя, указывающего на ту же память, что и p1 указывает на то, если вы хотите освободить вызов памяти на p1, но не p2, но можно освободить память, вызвав delete на p1 или p2.

если вы звоните удалить на p1 p2 затем сделать, чтобы указать нулевое значение, чтобы не разыменования его по ошибке, потому что написание:

delete p1; 
*p2 = 1; 

приведет к неопределенному поведению.

Смежные вопросы