C удалить объект из памяти

Обновлено: 07.07.2024

Затем после махинаций с ним мне нужно его принудительно удалить, как это сделать?

Лучший ответ по мнению автора

Андрей FaceOff

и деструкторы и IDisposable нужны тогда когда собственный класс захватывает внешние ресурсы долговременно и надо гарантировать их освобождение при удалении экземпляра класса неизвестно когда внешним по отношению к нему кодом

вообще это следующий уровень сложности

а удаление экземпляра класса как таковое — обнуление все ссылок на него

и ни чего более

pps автоматическая сборка мусора как раз и является достоинством во многих системах программирования

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

автоматическая сборка мусора позволяет не тратить время разработку кода удаления и сосредоточиться на прикладных аспектах программы

платой за это является затрата вычислительных ресурсов на автоматическую фоновую обработку сборки мусора. но обычно это мизерные затраты на фоне общего обьема вычислений

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

и что еще важнее — избавляет от большого риска ошибок при ручном программировании удаления обьектов

ppps ответ конкретно для приведенного фрагмента кода

при условии что ни где не было конструкций типа

var MyA nother Object = myClassObject;

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

myClassObject = new MyClass();

var MyAnotherObject = myClassObject;

по окончанию такого кода обьект перестанет существовать

так как нет ссылок на него, остается только фрагмент памяти который он занимал

в качестве этого обьекта он уже не может быть использован в принципе

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

Создаю указатель на экземпляр класса, вызываю конструктор, переменная "a" содержит число 2. Вызываю деструктор (в котором это "a" должно стать равным 1). Сначала ставлю проверку, существует ли указатель на экземпляр класса. Оказалось, что мало того, что он существует после деструктора, так ещё и в "a" лежит число 0. Выходит, в памяти начинают лежать ноли, но она всё ещё занята программой? Как удалить из памяти весь экземпляр класса? Если я заменю ссылку на класс (в данном случае "aa") на отсутствующую, то (по логике) память это не освободит, а лишь не даст мне больше управлять этой памятью по этой ссылке.

Стандартное заблуждение, что после delete переменная-указатель должна обнуляться. Интересно, откуда у нее "ноги растут"? @Harry Полагаю, из обычной человеческой логики. Удаляя ты хочешь удалить. Но вопрос не в этом. Из памяти экземпляр класса действительно удалён после delete или просто все его ячейки памяти принимают значение 0? Он именно удален, а что его ячейки принимают какое-то значение - так об этом в стандарте ничего не говорится. @Глеб: Что означает фраза "ставлю проверку, существует ли указатель на экземпляр класса". Почему вы решили, что эта проверка проверяет "существует ли указатель"? delete aa только удалило объект, на который указатель указывал. delete aa ничего не делает с самим указателем. Указатель у вас - локальная переменная. Он будет существовать до конца блока. @AnT я решил, что delete (как следует из перевода слова) именно удаляет, и что будет не только освобождена память, но и указатель станет указывать на NULL (ведь delete применяемо именно к указателю). В таком случае if от указателя равного NULL вернул бы false. Это и есть суть проверки, оказалось, что в указателе лежал не NULL. В lua удаление таблицы и затем проверка указателя на таблицу возвращала false, вот и заинтересовало, как дела в плюсах, почему адрес не NULL, и как тогда освобождать память.

[basic.stc]/4

When the end of the duration of a region of storage is reached, the values of all pointers representing the address of any part of that region of storage become invalid pointer values. Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior.

После delete aa; , указатель aa становится невалидным, и поведение if (aa) зависит от компилятора.

Что более важно, следующая строка - aa->a - вызывает неопределенное поведение, из-за доступа через невалидный указатель.

Раз поведение не определено, смысла рассуждать, почему вы видите в консоли 0 или что-то еще - немного.

Сначала ставлю проверку, существует ли указатель на экземпляр класса.

delete не обнуляет указатель. Так что проверка - не работает.

мало того, что он существует после деструктора

Он не "существует". Объект перестает существовать после вызова на нем деструктора 1 .

Вот только в языке нет способа проверить, указывает указатель на "существующий" объект, или нет.

a - тоже не существует. Читать из него - неопределенное поведение.

Выходит, в памяти начинают лежать ноли, но она всё ещё занята программой? Как удалить из памяти весь экземпляр класса?

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

Доброго времени суток всем.
Столкнуться с такой проблемой - надо удалить экземпляр объекта :

В итоге попробовал такой метод:


Интересно получается.
Ниже - код и вывод в консоль, в общем все то, что побудило меня создать эту тему.


Выше уже сказал, но заранее повторюсь - почему мы не можем удалить объект по имени объекта, разве это не есть как раз указатель на объект?

Огромное спасибо всем, кто отпишется по данному вопросу.

  • Вопрос задан более трёх лет назад
  • 7733 просмотра
̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻̻ BaseClass BaseClassObj;
Объект создаётся на стеке и будет уничтожен(просто сдвинут указатель на вершину стека по факту) при выходе из области видимости(функции), попытки удалить его через delete будут приводить к UB, т.е. сказать, что произойдёт нельзя, да и незачем.

Ukio_G

> Объект BaseClassObj будет удален только по завершению программы.
BaseClass baseClassObj;
Здесь создаётся объект на стеке. Имя — это просто имя объекта. Никаких указателей здесь нет. Как только мы покинем блок (любым образом: штатно выйти, goto, break, выброс аварии — кроме «жёсткого» выхода из программы функциями типа exit), у объекта автоматически исполнится деструктор и прямой вызов не нужен. Блок, то есть подпрограмму BaseClassPresentation.

BaseClass *BaseClassObjPtr = new BaseClass(2);
Здесь BaseClassObjPtr это имя указателя (а не указатель на указатель). Объект создаётся в динамической памяти, и его придётся уничтожать вручную. Многое в Си++11 сделали для того, чтобы подобные объекты уничтожались не вручную, а всё теми же автодеструкторами.
Это уже маленький объект со своим деструктором. А в деструкторе находится delete, и он сработает, как только программа выйдет из своего блока.

То, что вы хотите, иногда бывает нужно, и я вижу этому две причины.
1. Объект управляет какими-то сложными и важными ресурсами: большим количеством памяти, файлом, мьютексом… И этот важный ресурс бывает нужно освободить раньше, чем наступит деструктор. Например, у любого файлового потока есть функция close() — она закрывает файл.
2. У нас сложное и хитрое управление памятью, когда приходится использовать placement new и прямой вызов деструктора. Скажу честно, не использовал никогда. Как и 90% программистов на Си++.

Статическое выделение памяти выполняется для статических и глобальных переменных. Память выделяется один раз (при запуске программы) и сохраняется на протяжении работы всей программы.

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

Динамическое выделение памяти является темой этого урока.

Динамическое выделение переменных

Как статическое, так и автоматическое распределение памяти имеют два общих свойства:

Размер переменной/массива должен быть известен во время компиляции.

Выделение и освобождение памяти происходит автоматически (когда переменная создается/уничтожается).

В большинстве случаев с этим всё ОК. Однако, когда дело доходит до работы с пользовательским вводом, то эти ограничения могут привести к проблемам.

Например, при использовании строки для хранения имени пользователя, мы не знаем наперед насколько длинным оно будет, пока пользователь его не введет. Или нам нужно создать игру с непостоянным количеством монстров (во время игры одни монстры умирают, другие появляются, пытаясь, таким образом, убить игрока).

Если нам нужно объявить размер всех переменных во время компиляции, то самое лучшее, что мы можем сделать — это попытаться угадать их максимальный размер, надеясь, что этого будет достаточно:

char name [ 30 ] ; // будем надеяться, что пользователь введет имя длиной менее 30 символов! Polygon rendering [ 40000 ] ; // этому 3D-рендерингу лучше состоять из менее чем 40000 полигонов!

Это плохое решение, по крайней мере, по трем причинам:

Во-первых, теряется память, если переменные фактически не используются или используются, но не все. Например, если мы выделим 30 символов для каждого имени, но имена в среднем будут занимать по 15 символов, то потребление памяти получится в два раза больше, чем нам нужно на самом деле. Или рассмотрим массив rendering : если он использует только 20 000 полигонов, то память для других 20 000 полигонов фактически тратится впустую (т.е. не используется)!

В Visual Studio это можно проверить, запустив следующий фрагмент кода:

int array [ 1000000000 ] ; // выделяем 1 миллиард целочисленных значений

Лимит в 1МБ памяти может быть проблематичным для многих программ, особенно где используется графика.

Для динамического выделения памяти одной переменной используется оператор new:

new int ; // динамически выделяем целочисленную переменную и сразу же отбрасываем результат (так как нигде его не сохраняем)

В примере, приведенном выше, мы запрашиваем выделение памяти для целочисленной переменной из операционной системы. Оператор new возвращает указатель, содержащий адрес выделенной памяти.

Для доступа к выделенной памяти создается указатель:

int * ptr = new int ; // динамически выделяем целочисленную переменную и присваиваем её адрес ptr, чтобы затем иметь доступ к ней

Затем мы можем разыменовать указатель для получения значения:

* ptr = 8 ; // присваиваем значение 8 только что выделенной памяти

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

Как работает динамическое выделение памяти?

На вашем компьютере имеется память (возможно, большая её часть), которая доступна для использования программами. При запуске программы ваша операционная система загружает эту программу в некоторую часть этой памяти. И эта память, используемая вашей программой, разделена на несколько частей, каждая из которых выполняет определенную задачу. Одна часть содержит ваш код, другая используется для выполнения обычных операций (отслеживание вызываемых функций, создание и уничтожение глобальных и локальных переменных и т.д.). Мы поговорим об этом чуть позже. Тем не менее, большая часть доступной памяти компьютера просто находится в ожидании запросов на выделение от программ.

Когда вы динамически выделяете память, то вы просите операционную систему зарезервировать часть этой памяти для использования вашей программой. Если ОС может выполнить этот запрос, то возвращается адрес этой памяти обратно в вашу программу. С этого момента и в дальнейшем ваша программа сможет использовать эту память, как только пожелает. Когда вы уже выполнили с этой памятью всё, что было необходимо, то её нужно вернуть обратно в операционную систему, для распределения между другими запросами.

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

Освобождение памяти

Когда вы динамически выделяете переменную, то вы также можете её инициализировать посредством прямой инициализации или uniform-инициализации (в С++11):

int * ptr1 = new int ( 7 ) ; // используем прямую инициализацию int * ptr2 = new int < 8 >; // используем uniform-инициализацию

Когда уже всё, что требовалось, выполнено с динамически выделенной переменной — нужно явно указать для С++ освободить эту память. Для переменных это выполняется с помощью оператора delete:

// Предположим, что ptr ранее уже был выделен с помощью оператора new delete ptr ; // возвращаем память, на которую указывал ptr, обратно в операционную систему ptr = 0 ; // делаем ptr нулевым указателем (используйте nullptr вместо 0 в C++11)

Оператор delete на самом деле ничего не удаляет. Он просто возвращает память, которая была выделена ранее, обратно в операционную систему. Затем операционная система может переназначить эту память другому приложению (или этому же снова).

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

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

Висячие указатели

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

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

int * ptr = new int ; // динамически выделяем целочисленную переменную * ptr = 8 ; // помещаем значение в выделенную ячейку памяти delete ptr ; // возвращаем память обратно в операционную систему, ptr теперь является висячим указателем std :: cout << * ptr ; // разыменование висячего указателя приведет к неожиданным результатам delete ptr ; // попытка освободить память снова приведет к неожиданным результатам также

В программе, приведенной выше, значение 8 , которое ранее было присвоено динамической переменной, после освобождения может и далее находиться там, а может и нет. Также возможно, что освобожденная память уже могла быть выделена другому приложению (или для собственного использования операционной системы), и попытка доступа к ней приведет к тому, что операционная система автоматически прекратит выполнение вашей программы.

Процесс освобождения памяти может также привести и к созданию нескольких висячих указателей. Рассмотрим следующий пример:

int * ptr = new int ; // динамически выделяем целочисленную переменную int * otherPtr = ptr ; // otherPtr теперь указывает на ту же самую выделенную память, что и ptr delete ptr ; // возвращаем память обратно в операционную систему. ptr и otherPtr теперь висячие указатели // Однако, otherPtr по-прежнему является висячим указателем!

Есть несколько рекомендаций, которые могут здесь помочь:

Во-первых, старайтесь избегать ситуаций, когда несколько указателей указывают на одну и ту же часть выделенной памяти. Если это невозможно, то выясните, какой указатель из всех «владеет» памятью (и отвечает за её удаление), а какие указатели просто получают доступ к ней.

Правило: Присваивайте удаленным указателям значение 0 (или nullptr в C++11), если они не выходят из области видимости сразу же после удаления.

Оператор new

При запросе памяти из операционной системы в редких случаях она может быть не выделена (т.е. её может и не быть в наличии).

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

Во многих случаях процесс генерации исключения оператором new (как и сбой программы) нежелателен, поэтому есть альтернативная форма оператора new, которая возвращает нулевой указатель, если память не может быть выделена. Нужно просто добавить константу std::nothrow между ключевым словом new и типом данных:

int * value = new ( std :: nothrow ) int ; // указатель value станет нулевым, если динамическое выделение целочисленной переменной не выполнится

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

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

int * value = new ( std :: nothrow ) int ; // запрос на выделение динамической памяти для целочисленного значения if ( ! value ) // обрабатываем случай, когда new возвращает null (т.е. память не выделяется)

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

Нулевые указатели и динамическое выделение памяти

Нулевые указатели (указатели со значением 0 или nullptr ) особенно полезны в процессе динамического выделения памяти. Их наличие как бы сообщаем нам: «Этому указателю не выделено никакой памяти». А это, в свою очередь, можно использовать для выполнения условного выделения памяти:

// Если для ptr до сих пор не выделено памяти, то выделяем её

Удаление нулевого указателя ни на что не влияет. Таким образом, в следующем нет необходимости:

Вместо этого вы можете просто написать:

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

Утечка памяти

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

Здесь мы динамически выделяем целочисленную переменную, но никогда не освобождаем память через использование оператора delete. Поскольку указатели следуют всем тем же правилам, что и обычные переменные, то, когда функция завершит свое выполнение, ptr выйдет из области видимости. Поскольку ptr — это единственная переменная, хранящая адрес динамически выделенной целочисленной переменной, то, когда ptr уничтожится, больше не останется указателей на динамически выделенную память. Это означает, что программа «потеряет» адрес динамически выделенной памяти. И в результате эту динамически выделенную целочисленную переменную нельзя будет удалить.

Это называется утечкой памяти. Утечка памяти происходит, когда ваша программа теряет адрес некоторой динамически выделенной части памяти (например, переменной или массива), прежде чем вернуть её обратно в операционную систему. Когда это происходит, то программа уже не может удалить эту динамически выделенную память, поскольку больше не знает, где выделенная память находится. Операционная система также не может использовать эту память, поскольку считается, что она по-прежнему используется вашей программой.

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

Читайте также: