Почему использование ' new ' вызывает утечку памяти?

сначала я выучил C#, а теперь я начинаю с C++. Как я понимаю, оператор new в C++ не похож на тот, что в C#.

вы можете объяснить причину утечки памяти в этом примере кода?

class A { ... };
struct B { ... };

A *object1 = new A();
B object2 = *(new B());

9 ответов


что происходит

когда вы пишите T t; вы создаете объект типа T С автоматическая длительность хранения. Он будет очищаться автоматически, когда он выходит за рамки.

когда вы пишите new T() вы создаете объект типа T С динамическая память продолжительность. Он не будет очищаться автоматически.

new without cleanup

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

newing with delete

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

newing with deref

что нужно делать

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

A a; // a new object of type A
B b; // a new object of type B

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

template <typename T>
class automatic_pointer {
public:
    automatic_pointer(T* pointer) : pointer(pointer) {}

    // destructor: gets called upon cleanup
    // in this case, we want to use delete
    ~automatic_pointer() { delete pointer; }

    // emulate pointers!
    // with this we can write *p
    T& operator*() const { return *pointer; }
    // and with this we can write p->f()
    T* operator->() const { return pointer; }

private:
    T* pointer;

    // for this example, I'll just forbid copies
    // a smarter class could deal with this some other way
    automatic_pointer(automatic_pointer const&);
    automatic_pointer& operator=(automatic_pointer const&);
};

automatic_pointer<A> a(new A()); // acts like a pointer, but deletes automatically
automatic_pointer<B> b(new B()); // acts like a pointer, but deletes automatically

newing with automatic_pointer

это распространенная идиома, которая идет под не очень описательным именем RAII (Приобретение Ресурсов Инициализация). Когда вы приобретаете ресурс, который нуждается в очистке, вы вставляете его в объект с автоматической продолжительностью хранения, чтобы не нужно беспокоиться о чистке. Это относится к любому ресурсу, будь то память, открытые файлы, сетевые подключения или что угодно.

этой automatic_pointer вещь уже существует в различных формах, я только что предоставил ее, чтобы привести пример. Очень похожий класс существует в стандартной библиотеке под названием std::unique_ptr.

есть также старый (pre-c++11) с именем auto_ptr но теперь он устарел, потому что он имеет странное поведение копирования.

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


пошаговое объяснение:

// creates a new object on the heap:
new B()
// dereferences the object
*(new B())
// calls the copy constructor of B on the object
B object2 = *(new B());

таким образом, к концу этого у вас есть объект в куче без указателя на него, поэтому его невозможно удалить.

другой пример:

A *object1 = new A();

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

delete object1;

в C++ есть объекты с автоматическим хранением, созданные в стеке, которые автоматически удаляются, и объекты с динамическим хранением, на куча, которую вы выделяете с new и требуется, чтобы освободить себя с delete. (это все грубо говоря)

думаю, что вы должны иметь delete для каждого объекта выделяется с new.

редактировать

думаю, object2 не обязательно утечка памяти.

следующий код просто, чтобы сделать точку, это плохая идея, никогда не нравится код, как это:

class B
{
public:
    B() {};   //default constructor
    B(const B& other) //copy constructor, this will be called
                      //on the line B object2 = *(new B())
    {
        delete &other;
    }
}

в этом случае, так как other передается по ссылке, то это будет объект, на который указывает new B(). Поэтому, получая свой адрес по &other и удаление указателя освободит память.

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


учитывая два "объекта":

obj a;
obj b;

они не будут занимать одно и то же место в памяти. Другими словами,&a != &b

присвоение значения одного другому не изменит их местоположение, но изменит их содержимое:

obj a;
obj b = a;
//a == b, but &a != &b

интуитивно указатель "объекты" работают одинаково:

obj *a;
obj *b = a;
//a == b, but &a != &b

теперь, давайте посмотрим на ваш пример:

A *object1 = new A();

это присвоение значения new A() to object1. Значение является указателем, означающим object1 == new A(), а &object1 != &(new A()). (Обратите внимание, что этот пример не является допустимым кодом, он предназначен только для объяснения)

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


для вас второй пример, вы копируете заостренный на объект. Значение-это содержимое этого объекта, а не фактический указатель. Как и в любом другом случае, &object2 != &*(new A()).

B object2 = *(new B());

мы потеряли указатель на выделенную память, и поэтому мы не можем освободить его. delete &object2; может показаться, что это сработает, но ведь &object2 != &*(new A()), это не эквивалентно delete (new A()) и так инвалид.


В C# и Java вы используете new Для создания экземпляра любого класса, а затем вам не нужно беспокоиться об его уничтожении позже.

C++ также имеет ключевое слово "new", которое создает объект, но в отличие от Java или C#, это не единственный способ создать объект.

C++ имеет два механизма для создания объекта:

  • автоматическая
  • динамический

при автоматическом создании вы создаете объект в среде с областью видимости: - в функция или - как член класса (или структуры).

в функции вы бы создали его таким образом:

int func()
{
   A a;
   B b( 1, 2 );
}

в классе вы обычно создаете его таким образом:

class A
{
  B b;
public:
  A();
};    

A::A() :
 b( 1, 2 )
{
}

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

в последнем случае объект b уничтожается вместе с экземпляром A, в котором он является a член.

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

одним из таких объектов является shared_ptr, который вызовет логику "deleter", но только когда все экземпляры shared_ptr которые разделяют объект, уничтожаются.

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

ваши деструкторы не должны кидать исключений.

Если вы это сделаете, у вас будет мало утечек памяти.


B object2 = *(new B());

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

object2-это переменная типа B, хранящаяся по адресу 1 (Да, здесь я выбираю произвольные числа). С правой стороны вы попросили новый B или указатель на объект типа B. программа с радостью дает вам это и назначает ваш новый B для адреса 2, а также создает указатель в адресе 3. Теперь единственный способ получить доступ к данным в адресе 2-через указатель в адресе 3. Далее разыменовал указатель с помощью * чтобы получить данные, на которые указывает указатель (данные в адресе 2). Это эффективно создает копию этих данных и назначает ее object2, назначенному в адресе 1. Помните, это копия, а не оригинал.

теперь, вот проблема:

вы никогда не сохраняли этот указатель в любом месте, где вы можете его использовать! Как только это назначение завершено, указатель (память в address3, который вы использовали для доступа к address2) выходит за рамки и вне твоей досягаемости! Вы больше не можете вызывать delete на нем и, следовательно, не можете очистить память в address2. То, что у вас осталось, - это копия данных из address2 в address1. Две одинаковые вещи остались в памяти. К одному вы можете получить доступ, к другому-нет (потому что вы потеряли путь к нему). Вот почему это утечка памяти.

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


при создании object2 вы создаете копию объекта, который вы создали с помощью new, но вы также теряете (никогда не назначенный) указатель (поэтому нет способа удалить его позже). Чтобы избежать этого, вам придется сделать object2 ссылка.


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

в двух случаях:

A *object1 = new A();

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

и здесь

B object2 = *(new B());

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


Это эта строка, которая немедленно протекает:

B object2 = *(new B());

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

эта строка не сразу дырявый:

A *object1 = new A();

будет утечка, если вы никогда deleted object1 хотя.


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

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

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

Если ваша программа выделяет память и не удаляет ее (она просто перестает ее использовать), тогда компьютер думает, что память все еще используется и не позволит никому другому использовать ее. Это утечка памяти.

Это не точная аналогия, но это может помочь.