• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

wycieki pamięci podczas dealokacji wektora wskaźników

Object Storage Arubacloud
0 głosów
474 wizyt
pytanie zadane 31 grudnia 2019 w C i C++ przez fedora Użytkownik (500 p.)

Witam,

Mam problem z dealokacją wskaźnika na wektor wskaźników, który jest polem pewnej klasy.

Według StackOverflow wystarczy w pętli dla każdego elementu takiego wektora użyć delete, a na końcu użyć metody clear(). Na cppreference (link: https://en.cppreference.com/w/cpp/container/vector/clear) jest napisane, że wszelkie wskaźniki zostaną uniważnione i nie wiem czy to oznacza, że pamięć zostaje zwolniona, czy tylko stracę adresy na zaalokowane miejsca na stercie?

Wreszcie valgrind i debugger z visuala2017 wykrywają wycieki mimo zastosowania wszystkich sensownych (według mnie) kombinacji usuwania tego wektora.

Tym krótkim przykładem pokażę na czym polega problem.

class Element {
public:
    Element(int num) { val = new int[num]; }

    ~Element() { delete[] val; }
    
private:
    int *val;
};

class Base {
public:
    Base(int size) { 
        list = new std::vector<Element*>(size, new Element(1000)); 
    }

    ~Base() { 
        /* odkomentowanie spowoduje podwojne zwolnienie pamieci 
         * mimo ze teoretycznie teraz sa wycieki
         
        for(int i = 0; i < list->size(); ++i) {
            delete list->at(i);
        }
        */
        list->clear();
        delete list;
    }


private:
    std::vector<Element*> *list;
};

Teraz dla przykładowej instancji klasy Base w mainie

int main() {
    Base *test = new Base(1000);

    delete test;
    return 0;
}

valgrind zwraca taki komunikat:

==4809== HEAP SUMMARY:
==4809==    in use at exit: 4,008 bytes in 2 blocks
==4809==   total heap usage: 6 allocs, 4 frees, 84,744 bytes allocated
==4809== 
==4809== LEAK SUMMARY:
==4809==    definitely lost: 8 bytes in 1 blocks
==4809==    indirectly lost: 4,000 bytes in 1 blocks
==4809==      possibly lost: 0 bytes in 0 blocks
==4809==    still reachable: 0 bytes in 0 blocks
==4809==         suppressed: 0 bytes in 0 blocks

Jak więc powinien wyglądać destruktor klasy Base, by nie było wycieków? Dodatkowo nie chcę korzystać z inteligentnych wskaźników.

Z góry dziękuję za każdą pomoc i sugestię.

2 odpowiedzi

+1 głos
odpowiedź 31 grudnia 2019 przez adrian17 Ekspert (344,860 p.)
wybrane 31 grudnia 2019 przez fedora
 
Najlepsza

Chwila... po co w ogóle dynamicznie alokujesz vector? Nie ma potrzeby by to robić, w dodatku to niejako zaprzecza połowie celu jego istnienia. Podobnie nie rozumiem czemu Element dynamicznie alokuje tablicę, jeśli tam też mógłbyś użyć vectora. Podobnie nie rozumiem czemu vector przechowuje wskaźniki... :( Łącznie są tu tu cztery warstwy widocznych wskaźników (plus jedna ukryta w vectorze), a mogłoby być zero (plus dwa w vectorach).

W każdym razie...

list = new std::vector<Element*>(size, new Element(1000));

Tutaj robisz vector wskaźników, ale `new Element(1000)` wykonuje się raz i do każdego elementu vectora przypisujesz ten sam wskaźnik.

        for(int i = 0; i < list->size(); ++i) {
            delete list->at(i);
        }

Z zakomentowanym tym blokiem, nigdy nie zwalniasz wskaźnika zaalokowanego przez `new Element(1000)` - wyciek.

Po odkomendowaniu tego bloku, w pętli robisz delete na każdym elemencie vectora - ale każdy element vectora to ten sam wskaźnik, więc wykonujesz kilkukrotnie delete na tym samym wskaźniku - błąd.

komentarz 31 grudnia 2019 przez fedora Użytkownik (500 p.)

Dziękuję za odpowiedź!

Jeśli chodzi o wskaźnik na wektor to zapewne masz racje - powinienem stworzyć go statycznie. Nie to było jednak moim celem.

W klasie element tworzę tablicę tylko żeby zaalokować pewien obszar na stercie. Chcę sprawdzać co się skasowało, a co nie.

Prawdziwym problemem okazał się konstruktor wektora, który tworzy wiele wskaźników na ten sam obiekt. Nie tego się spodziewałem. Dziękuję Ci, że swój poświęciłeś czas. Po zaproponowanych przez Ciebie zmianach kod wygląda tak i nie ma wycieków pamięci.

#include <vector>

class Element {
public:
    Element(int num) { val = new int[num]; }

    ~Element() { delete[] val; }
    
private:
    int *val;
};

class Base {
public:
    Base(int size) 
    : list(size) 
    { 
        for(int i = 0; i < size; ++i) {
            list[i] = new Element(1000);
        }
    }

    ~Base() { 
        for(int i = 0; i < list.size(); ++i) {
            delete list[i];
        }
        list.clear();
    }


private:
    std::vector<Element*> list;
};

int main() {
    Base base(1000);
	return 0;
}

 

komentarz 31 grudnia 2019 przez adrian17 Ekspert (344,860 p.)
OK. Tylko chciałem podkreślić, że w tym przypadku nie ma dobrego powodu, by używać vector<Element*> zamiast vector<Element>.
komentarz 31 grudnia 2019 przez fedora Użytkownik (500 p.)

Oczywiście, że nie ma laugh Tak samo jak sens istnienia całej klasy Element, która tylko alokuje obszar w pamięci i nie pozwala z niej korzystać. Chodziło mi tylko o zrozumienie koncepcji dealokacji takiej struktury. Bo chyba nie da się zaprzeczyć, że czasem przydaje się mieć wektor wskaźników na jakieś obiekty.

W każdym razie.. Szczęśliwego nowego roku! wink

komentarz 31 grudnia 2019 przez adrian17 Ekspert (344,860 p.)
Nazwajem :)
0 głosów
odpowiedź 31 grudnia 2019 przez mokrowski Mędrzec (155,460 p.)

Pomijając fakt tego że program nie jest przemyślany...

#include <vector>

class Element {
public:
    Element(int num)
        : val(new int[num]) {}

    ~Element() { delete[] val; }

private:
    int *val;
};

class Base {
public:
    explicit Base(int size)
        : list(new std::vector<Element*>(size)) {
        for(auto& ptr: *list) {
            ptr = new Element(1000);
        }
    }

    ~Base() {
        for(auto& ptr: *list) {
            delete ptr;
        }
        delete list;
    }
private:
    std::vector<Element*> *list;
};

int main() {
    Base *test = new Base(1000);

    delete test;
}

 

komentarz 31 grudnia 2019 przez fedora Użytkownik (500 p.)
Hej, Dzięki za odpowiedź.

Jak napisał kolega wyżej - prawdziwy problem tkwił w złym użyciu konstruktora wektora. Cała reszta to tylko przykład - nie chciałem pisać długiego, skomplikowanego i korzystającego z najnowszych rozwiązań programu tylko, żeby pojąć jedną podstawową zasadę. Mimo to dzięki za odpowiedź!

Podobne pytania

0 głosów
1 odpowiedź 392 wizyt
0 głosów
0 odpowiedzi 181 wizyt
+1 głos
2 odpowiedzi 1,015 wizyt

92,576 zapytań

141,426 odpowiedzi

319,652 komentarzy

61,961 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...