Dlaczego użycie "nowego" bo wycieki pamięci?

Najpierw nauczyłem się C#, a teraz zaczynam od C++. Jak rozumiem operator new W C++ nie jest podobny do tego w C#.

Czy możesz wyjaśnić przyczynę wycieku pamięci w tym przykładowym kodzie?
class A { ... };
struct B { ... };

A *object1 = new A();
B object2 = *(new B());
Author: Xeo, 2012-01-12

9 answers

Co się dzieje

Kiedy piszesz T t; tworzysz obiekt typu Tz automatycznym czasem przechowywania. Zostanie automatycznie oczyszczony, gdy wyjdzie poza zasięg.

Podczas pisania new T() tworzysz obiekt typu Tz dynamicznym czasem przechowywania. Nie zostanie wyczyszczone automatycznie.

Nowy bez sprzątania

Musisz przekazać do niego wskaźnik delete, aby go wyczyścić up:

newing with delete

Jednak twój drugi przykład jest gorszy: dereferujesz wskaźnik i robisz kopię obiektu. W ten sposób tracisz wskaźnik do obiektu utworzonego za pomocą new, więc nigdy nie możesz go usunąć, nawet jeśli chcesz!

newing with deref

Co powinieneś zrobić

Powinieneś preferować Automatyczny czas przechowywania. Potrzebujesz nowego obiektu, po prostu napisz:

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

Jeśli potrzebujesz dynamicznego czasu przechowywania, Zapisz wskaźnik do obiekt alokowany w obiekcie automatycznego czasu przechowywania, który usuwa go automatycznie.

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

nowe z automatic_pointer

Jest to często spotykany idiom, który nosi niezbyt opisową nazwę RAII ( pozyskiwanie zasobów jest inicjalizacją ). Po zakupie zasobu, który wymaga czyszczenia, trzymasz go w obiekcie o automatycznym czasie przechowywania, więc nie musisz się martwić o czyszczenie go. Dotyczy to każdego Zasobu, czy to Pamięci, otwartych plików, połączeń sieciowych lub cokolwiek innego fantazyjne.

Ta automatic_pointer rzecz istnieje już w różnych formach, podałem ją tylko dla przykładu. Bardzo podobna Klasa istnieje w bibliotece standardowej o nazwie std::unique_ptr.

Istnieje również stary (pre-C++11) o nazwie auto_ptr, ale teraz jest przestarzały, ponieważ ma dziwne zachowanie kopiowania.

I są jeszcze mądrzejsze przykłady, takie jak std::shared_ptr, które pozwalają na wiele wskaźników do tego samego obiektu i czyszczą go tylko wtedy, gdy ostatni wskaźnik zostanie zniszczony.

 465
Author: R. Martinho Fernandes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-17 21:30:07

Wyjaśnienie krok po kroku:

// 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());

Więc pod koniec tego, masz Obiekt na stercie bez wskaźnika do niego, więc nie można go usunąć.

Druga próbka:

A *object1 = new A();

Jest wyciekiem pamięci tylko wtedy, gdy zapomnisz delete przydzielonej pamięci:

delete object1;

W C++ są obiekty z automatycznym przechowywaniem, te utworzone na stosie, które są automatycznie usuwane, oraz obiekty z dynamicznym przechowywaniem, na stosie, które przydzielasz za pomocą new i są zobowiązani do uwolnienia się z delete. (to wszystko jest z grubsza powiedziane)

Pomyśl, że powinieneś mieć delete dla każdego obiektu przydzielonego new.

EDIT

To nie musi być wyciek pamięci.

Poniższy kod jest po prostu do rzeczy, to zły pomysł, nigdy nie lubię kodu takiego jak ten:

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;
    }
}

W tym przypadku, ponieważ {[10] } jest przekazywana przez odniesienie, będzie to dokładny obiekt wskazywany przez new B(). Dlatego uzyskanie jego adresu przez &other i usunięcie wskaźnika uwolniłoby pamięć.

Ale nie mogę tego wystarczająco podkreślić, nie rób tego. Chodzi o to, żeby coś powiedzieć.

 35
Author: Luchian Grigore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-12 18:14:50

Podane dwa "obiekty":

obj a;
obj b;
Nie będą zajmować tego samego miejsca w pamięci. Innymi słowy, &a != &b

Przypisanie wartości jednego do drugiego nie zmieni ich lokalizacji, ale zmieni ich zawartość:

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

Intuicyjnie wskaźnik "obiekty" działa w ten sam sposób:

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

Spójrzmy teraz na twój przykład:]}
A *object1 = new A();

To przypisanie wartości new A() do object1. Wartość jest wskaźnikiem, co oznacza object1 == new A(), ale &object1 != &(new A()). (Zauważ, że to przykład nie jest poprawnym kodem, służy tylko do wyjaśnienia)

Ponieważ wartość wskaźnika jest zachowana, możemy zwolnić pamięć, do której wskazuje: delete object1; ze względu na naszą regułę, zachowuje się ona tak samo jak delete (new A());, która nie ma wycieku.


W drugim przykładzie kopiujesz obiekt wskazywany na obiekt. Wartością jest zawartość tego obiektu, a nie rzeczywisty wskaźnik. Jak w każdym innym przypadku, &object2 != &*(new A()).

B object2 = *(new B());

Straciliśmy wskaźnik do przydzielonej pamięci, a więc nie możemy uwolnij go. delete &object2; może wydawać się, że to zadziała, ale ponieważ &object2 != &*(new A()), nie jest równoważne delete (new A()) i jest tak nieważne.

 11
Author: Pubby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-12 18:34:08

W C# i Javie, używasz new do tworzenia instancji dowolnej klasy i nie musisz się martwić o jej późniejsze zniszczenie.

C++ ma również słowo kluczowe "new", które tworzy obiekt, ale w przeciwieństwie do Javy lub C#, nie jest to jedyny sposób na utworzenie obiektu.

C++ posiada dwa mechanizmy tworzenia obiektu:

  • automatyczny
  • dynamiczne

Z automatycznym tworzeniem tworzysz obiekt w środowisku o zasięgu: - w funkcji lub - jako członek klasy (lub struct).

W funkcji stworzyłbyś ją w ten sposób:

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

Wewnątrz klasy normalnie tworzysz ją w ten sposób:

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

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

W pierwszym przypadku obiekty są niszczone automatycznie po zakończeniu bloku zakresu. Może to być funkcja lub blok zakresu wewnątrz funkcji.

W tym ostatnim przypadku obiekt b jest niszczony wraz z instancją a, w której jest członkiem.

Obiekty są przydzielane z new, gdy trzeba kontrolować żywotność obiektu, a następnie wymaga usunięcia, aby go zniszczyć. Dzięki technice znanej jako RAII, zajmujesz się usuwaniem obiektu w miejscu, w którym go tworzysz, umieszczając go w automatycznym obiekcie i czekasz na efekt destruktora tego automatycznego obiektu.

Jednym z takich obiektów jest shared_ptr, który wywoła logikę "deleter", ale tylko wtedy, gdy wszystkie instancje shared_ptr, które dzielą obiekt, zostaną zniszczone.

Ogólnie, podczas gdy twój kod może mieć wiele wywołań do new, powinieneś mieć ograniczone wywołania do usunięcia i zawsze upewnij się, że są one wywoływane z destruktorów lub obiektów "deleter", które są umieszczane w inteligentnych wskaźnikach.

Twoje destruktory nie powinny również wyrzucać WYJĄTKÓW.

Jeśli to zrobisz, będziesz miał kilka wycieków pamięci.
 9
Author: CashCow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-12 18:17:20
B object2 = *(new B());
Ta linia jest przyczyną wycieku. Rozdzielmy to trochę..

Object2 jest zmienną typu B, przechowywaną pod adresem powiedzmy 1 (tak, wybieram tutaj dowolne liczby). Po prawej stronie poprosiłeś o nowe B, lub wskaźnik do obiektu typu B. program chętnie Ci to daje i przypisuje twoje nowe B do adresu 2, a także tworzy wskaźnik w adresie 3. Teraz jedynym sposobem dostÄ ™ pu do danych w adresie 2 jest wskaĺşnik w adresie 3. Następny, ty dereferenced wskaźnik za pomocą *, Aby uzyskać dane, na które wskazuje wskaźnik (dane w adresie 2). To skutecznie tworzy kopię tych danych i przypisuje je obiektowi2, przypisanemu w adresie 1. Pamiętaj, to kopia, nie oryginał.

Oto problem:

Nigdy nie przechowywałeś tego wskaźnika w dowolnym miejscu, w którym możesz go użyć! Gdy to zadanie zostanie zakończone, wskaźnik (pamięć w adresa3, której użyłeś do uzyskania dostępu do adresa2) jest poza zasięgiem i poza twoim zasięgiem! Możesz nie wywołuje już delete na nim i dlatego nie może wyczyścić pamięci w adres2. To, co ci zostało, to kopia danych z address2 w address1. Dwie takie same rzeczy są w pamięci. Do jednego masz dostęp, do drugiego nie możesz (ponieważ zgubiłeś ścieżkę do niego). Dlatego to wyciek pamięci.

Sugerowałbym zaczerpnięcie z twojego C# tła, abyś dużo poczytał o tym, jak działają wskaźniki W C++. Są to zaawansowane tematy i mogą zająć trochę czasu, aby zrozumieć, ale ich użycie będzie bądź dla Ciebie bezcenny.

 9
Author: MGZero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-12 21:09:20

Jeśli to ułatwi, pomyśl o pamięci komputera jak o hotelu, a programy są klientami, którzy wynajmują pokoje, gdy ich potrzebują.

Ten hotel działa tak, że rezerwujesz pokój i mówisz portierowi, kiedy wychodzisz.

Jeśli zaprogramujesz rezerwację pokoju i wyjedziesz bez mówienia portierowi, portier pomyśli, że pokój jest nadal używany i nie pozwoli nikomu z niego korzystać. W tym przypadku jest przeciek pokoju.

Jeśli twój program przydziela pamięć i robi nie usuwa go (po prostu przestaje go używać), a następnie komputer myśli, że pamięć jest nadal używana i nie pozwoli nikomu innemu jej używać. To wyciek pamięci.

To nie jest dokładna analogia, ale może pomóc.

 8
Author: Stefan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-08 13:38:17

Podczas tworzenia object2 tworzysz kopię obiektu, który utworzyłeś za pomocą new, ale tracisz również (nigdy nie przypisany) wskaźnik (więc nie ma sposobu, aby go później usunąć). Aby tego uniknąć, musisz zrobić object2 odniesienie.

 7
Author: Mario,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-12 18:05:15

Cóż, tworzysz wyciek pamięci, jeśli w pewnym momencie nie uwolnisz pamięci przydzielonej za pomocą operatora new, przekazując wskaźnik do tej pamięci operatorowi delete.

W dwóch powyższych przypadkach:

A *object1 = new A();

Tutaj nie używasz delete do uwolnienia pamięci, więc jeśli i kiedy twój wskaźnik object1 wyjdzie poza zasięg, będziesz miał wyciek pamięci, ponieważ straciłeś wskaźnik i nie możesz na nim użyć operatora delete.

I tutaj

B object2 = *(new B());

Jesteś odrzucenie wskaźnika zwracanego przez new B(), a więc nigdy nie może przekazać tego wskaźnika do delete, aby pamięć została uwolniona. Stąd kolejny wyciek pamięci.

 7
Author: razlebe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-12 18:07:27

To ta linia od razu przecieka:

B object2 = *(new B());

Tutaj tworzysz nowy obiekt B na stosie, a następnie tworzysz kopię na stosie. Ten, który został przydzielony na stercie, nie może być już dostępny, a tym samym wyciek.

Ta linia nie jest od razu przeciekająca:

A *object1 = new A();
Nie byłoby przecieku, jeśli nigdy nie delete d object1 chociaż.
 7
Author: mattjgalloway,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-12 18:20:43