Сначала я выучил C #, а теперь начинаю с C ++. Насколько я понимаю, оператор new
в C ++ не похож на оператор в C #.
Можете ли вы объяснить причину утечки памяти в этом примере кода?
class A { ... };
struct B { ... };
A *object1 = new A();
B object2 = *(new B());
Ответы:
Что происходит
Когда вы пишете,
T t;
вы создаете объект типаT
с автоматической продолжительностью хранения . Он будет очищен автоматически, когда выйдет за рамки.Когда вы пишете,
new T()
вы создаете объект типаT
с динамической продолжительностью хранения . Он не будет очищен автоматически.Вам нужно передать на него указатель
delete
, чтобы очистить его:Однако ваш второй пример хуже: вы разыменовываете указатель и делаете копию объекта. Таким образом, вы потеряете указатель на созданный объект
new
, и вы никогда не сможете его удалить, даже если бы захотели!Что ты должен делать
Вы должны предпочесть автоматическую продолжительность хранения. Нужен новый объект, просто напишите:
Если вам действительно нужна длительность динамического хранения, сохраните указатель на выделенный объект в объекте автоматической продолжительности хранения, который удалит его автоматически.
Это распространенная идиома, которая носит не очень информативное название RAII ( Resource Acquisition Is Initialization ). Когда вы приобретаете ресурс, который требует очистки, вы вставляете его в объект с автоматической продолжительностью хранения, поэтому вам не нужно беспокоиться об его очистке. Это применимо к любому ресурсу, будь то память, открытые файлы, сетевые соединения или что угодно.
Эта
automatic_pointer
штука уже существует в разных формах, я просто привел ее для примера. Очень похожий класс существует в стандартной библиотеке под названиемstd::unique_ptr
.Также есть старый (до C ++ 11), названный,
auto_ptr
но теперь он устарел из-за странного поведения копирования.А есть еще несколько более умных примеров, например
std::shared_ptr
, которые позволяют использовать несколько указателей на один и тот же объект и очищают его только после уничтожения последнего указателя.источник
*p += 2
, как с обычным указателем. Если бы он не возвращался по ссылке, он бы не имитировал поведение обычного указателя, что и является целью.Пошаговое объяснение:
Итак, к концу этого у вас есть объект в куче без указателя на него, поэтому его невозможно удалить.
Другой образец:
- это утечка памяти, только если вы забыли
delete
о выделенной памяти:В C ++ есть объекты с автоматическим хранилищем, созданные в стеке, которые автоматически удаляются, и объекты с динамическим хранилищем в куче, которые вы выделяете
new
и должны освобождать себяdelete
. (это все грубо говоря)Подумайте, что у вас должен быть
delete
для каждого объекта, выделенного сnew
.РЕДАКТИРОВАТЬ
Если подумать,
object2
не обязательно должна быть утечка памяти.Следующий код просто подчеркивает, это плохая идея, никогда не любите такой код:
В этом случае, поскольку
other
он передается по ссылке, это будет точный объект, на который указываетnew B()
. Следовательно, получение адреса&other
и удаление указателя освободит память.Но я не могу этого особо подчеркнуть, не делай этого. Это просто для того, чтобы подчеркнуть.
источник
Даны два «объекта»:
Они не будут занимать одно и то же место в памяти. Другими словами,
&a != &b
Присвоение значения одному другому не изменит их местоположение, но изменит их содержимое:
Интуитивно понятно, что указатели на «объекты» работают одинаково:
Теперь давайте посмотрим на ваш пример:
Это присвоение значения
new A()
кobject1
. Значение - это указатель, то естьobject1 == new A()
, но&object1 != &(new A())
. (Обратите внимание, что этот пример не является допустимым кодом, он предназначен только для объяснения)Поскольку значение указателя сохраняется, мы можем освободить память, на которую он указывает:
delete object1;
в соответствии с нашим правилом это ведет себя так же, какdelete (new A());
и в случае отсутствия утечки.Во втором примере вы копируете указанный объект. Значение - это содержимое этого объекта, а не фактический указатель. Как и в любом другом случае,
&object2 != &*(new A())
.Мы потеряли указатель на выделенную память, и поэтому мы не можем ее освободить.
delete &object2;
может показаться, что это сработает, но потому&object2 != &*(new A())
что это не эквивалентноdelete (new A())
и поэтому недействительно.источник
В C # и Java вы используете new для создания экземпляра любого класса, и тогда вам не нужно беспокоиться о его уничтожении позже.
В C ++ также есть ключевое слово new, которое создает объект, но, в отличие от Java или C #, это не единственный способ создания объекта.
В C ++ есть два механизма для создания объекта:
При автоматическом создании вы создаете объект в среде с заданной областью действия: - в функции или - как член класса (или структуры).
В функции вы должны создать это следующим образом:
Внутри класса вы обычно создаете его следующим образом:
В первом случае объекты уничтожаются автоматически при выходе из блока области видимости. Это может быть функция или блок области видимости внутри функции.
В последнем случае объект b уничтожается вместе с экземпляром A, членом которого он является.
Объекты выделяются с помощью new, когда вам нужно контролировать время жизни объекта, а затем требуется удаление для его уничтожения. С помощью техники, известной как RAII, вы заботитесь об удалении объекта в момент его создания, помещая его в автоматический объект, и ждете, пока деструктор этого автоматического объекта вступит в силу.
Одним из таких объектов является shared_ptr, который вызовет логику «удаления», но только тогда, когда все экземпляры shared_ptr, которые совместно используют объект, будут уничтожены.
В общем, хотя в вашем коде может быть много вызовов new, у вас должно быть ограниченное количество вызовов для удаления и всегда следует убедиться, что они вызываются из деструкторов или объектов «удаления», которые помещаются в интеллектуальные указатели.
Ваши деструкторы также никогда не должны создавать исключения.
Если вы сделаете это, у вас будет несколько утечек памяти.
источник
automatic
иdynamic
. Также естьstatic
.Эта линия является причиной утечки. Давайте немного разберем это ...
object2 - это переменная типа B, хранящаяся, скажем, по адресу 1 (да, я выбираю здесь произвольные числа). Справа вы запросили новый B или указатель на объект типа B. Программа с радостью предоставит его вам и присваивает вашему новому B адресу 2, а также создает указатель на адресе 3. Теперь, единственный способ получить доступ к данным в адресе 2 - через указатель в адресе 3. Затем вы разыменовали указатель, используя
*
для получения данных, на которые указывает указатель (данные в адресе 2). Это эффективно создает копию этих данных и назначает ее объекту 2, назначенному по адресу 1. Помните, что это КОПИЯ, а не оригинал.Теперь вот проблема:
На самом деле вы никогда не хранили этот указатель где-либо, где могли бы его использовать! Как только это назначение завершено, указатель (память в адресе 3, который вы использовали для доступа к адресу 2) выходит за рамки и вне вашей досягаемости! Вы больше не можете вызывать для него delete и, следовательно, не можете очистить память по адресу address2. У вас остается копия данных с адреса 2 в адресе 1. Две одинаковые вещи сидят в памяти. К одному вы можете получить доступ, к другому - нет (потому что вы потеряли к нему путь). Вот почему это утечка памяти.
Я бы посоветовал, исходя из вашего опыта работы с C #, много читать о том, как работают указатели в C ++. Это сложная тема, на освоение которой может потребоваться время, но их использование будет для вас неоценимо.
источник
Если это облегчает задачу, представьте, что память компьютера - это гостиница, а программы - это клиенты, которые снимают комнаты, когда они им нужны.
Этот отель работает так: вы бронируете номер и говорите носильщику, когда уезжаете.
Если вы запрограммируете номер и уйдете, не сказав носильщику, портье подумает, что комната все еще используется, и не позволит никому ее использовать. В этом случае есть утечка в помещении.
Если ваша программа выделяет память и не удаляет ее (она просто перестает ее использовать), тогда компьютер считает, что память все еще используется, и не позволит никому ее использовать. Это утечка памяти.
Это не точная аналогия, но она может помочь.
источник
При создании
object2
вы создаете копию объекта, который вы создали с помощью new, но вы также теряете (никогда не назначенный) указатель (поэтому нет возможности удалить его позже). Чтобы этого избежать, вам нужно сделатьobject2
ссылку.источник
Что ж, вы создаете утечку памяти, если в какой-то момент не освободите память, выделенную с помощью
new
оператора, передав указатель на эту памятьdelete
оператору.В ваших двух случаях выше:
Здесь вы не используете
delete
для освобождения памяти, поэтому, если и когда вашobject1
указатель выйдет за пределы области видимости, у вас будет утечка памяти, потому что вы потеряете указатель и не сможете использовать для негоdelete
оператор.И тут
вы отбрасываете указатель, возвращаемый
new B()
, и поэтому никогда не можете передать этот указательdelete
для освобождения памяти. Отсюда очередная утечка памяти.источник
Вот эта строка немедленно просачивается:
Здесь вы создаете новый
B
объект в куче, а затем создаете копию в стеке. Тот, который был выделен в куче, больше недоступен и, следовательно, утечка.Эта строка не сразу становится дырявой:
Там будет течь , если вы никогда не
delete
d ,object1
хотя.источник