Dokładne usuwanie z pamięci listy dwukierunkowej

0

Hej,
mam taką oto klasę, która jest listą dwukierunkową.

class lista{
	private:
		class element{
			//klasa pomocnicza, która jest niezbędna do poprawnej nawigacji między elementami
			public:
				element * poprzedni;
				element * nastepny;
				int unsigned wartosc;
			
				element(){
					poprzedni = NULL;
					nastepny = NULL;
					wartosc = 0;
				}
		};
	element * pierwszy;
	element * ostatni;
}; 

W programie używam jej tysiące razy i tak wielka pętla zżera mnóstwo RAM'u. Chcę więc poprawnie usunąć z pamięci całą listę za każdym razem.

Póki co, wykombinowałem takie rozwiązanie, jednakże to nie jest to - cały czas coś zostaje w pamięci. Czy moglibyście mi pomóc w lokalizacji problemu?

 	
//przed stworzeniem nowej listy
if ((pierwszy!=NULL)&&(ostatni!=NULL)){
	element *pom, *pom2;
	pom=pierwszy;
	for (int i=0;pom->nastepny!=NULL;){
		pom2=pom;
		delete pom;
		pom=pom2->nastepny;
		delete pom2;
	}
	delete pom;
	delete pom2;
	delete pierwszy;
	delete ostatni;
}

Bardzo dziękuje za każdą pomoc :-)

0

Ten kod jest zdecydowanie źle (dodałem komentarz)!

//przed stworzeniem nowej listy
if ((pierwszy!=NULL)&&(ostatni!=NULL)){
	element *pom, *pom2;
	pom=pierwszy;
	for (int i=0;pom->nastepny!=NULL;){
		pom2=pom;
		delete pom;
		pom=pom2->nastepny; // naprawdę nie masz tu crasha? Operujesz na usuniętym elemencie!
		delete pom2;
	}
	delete pom;
	delete pom2;
	delete pierwszy;
	delete ostatni;
}

W zasadzie to tak naprawdę nie wiadomo co to ma robić! Wygląda strasznie.

Najważniejsze to ustalić gdzie ci cieknie. Nie zdziwiłbym się gdyby się okazało, że obwiniasz kod który nie cieknie.
Albo zapoznasz się z narzędziami do analizy pamięci.

2

Użyj std::vector lub std::list jeśli profiler potwierdzi, że to lepsze rozwiązanie. Nie ma co wymyślać koła na nowo. Jeśli celem zadania jest napisanie listy, niech wskaźnik na następny element będzie typu unique_ptr<T>, problemy znikną.

0

Jeżeli nie jesteś pewny czy w argumencie do delete jest prawidłowy wskaźnik to ustaw go po każdym delete na NULL.
Czyli zamiast "delete wsk;" uzywaj "delete wsk;wsk = NULL;". O ile podwójne zwolnienie pamieci nie wróży niczego dobrego o tyle zwolnienie NULL'a nic nie powoduje.
Co do kodu warto zastanowić się co się tam dzieje

if ((pierwszy!=NULL)&&(ostatni!=NULL)){ 
    element *pom, *pom2;
    pom=pierwszy;
    for (int i=0;pom->nastepny!=NULL;){
        pom2=pom;							//kopia wskaznika
        pom=pom2->nastepny;					//kopia wskaznika nastepny, puki jeszcze istnieje
        delete pom2;							//usun z pamieci
    }
//ostatnia interacja, pozostal Ci ogon, a więc zwalniasz go i tylko go
    delete pom;								//lub     delete ostatni;
}
 
0
MarekR22 napisał(a):

Ten kod jest zdecydowanie źle (dodałem komentarz)!

//przed stworzeniem nowej listy
if ((pierwszy!=NULL)&&(ostatni!=NULL)){
	element *pom, *pom2;
	pom=pierwszy;
	for (int i=0;pom->nastepny!=NULL;){
		pom2=pom;
		delete pom;
		pom=pom2->nastepny; // naprawdę nie masz tu crasha? Operujesz na usuniętym elemencie!
		delete pom2;
	}
	delete pom;
	delete pom2;
	delete pierwszy;
	delete ostatni;
}

W zasadzie to tak naprawdę nie wiadomo co to ma robić! Wygląda strasznie.

Najważniejsze to ustalić gdzie ci cieknie. Nie zdziwiłbym się gdyby się okazało, że obwiniasz kod który nie cieknie.

Albo zapoznasz się z narzędziami do analizy pamięci.

Kurcze, faktycznie jest operacja na usuniętym elemencie, ale nic mi nie wywala :/ Nie wiem jak to rozwiązać.

Co ma robić? Ma usuwać listę z pamięci. Lista jest zbudowana z elementów wypisanych w pierwszym poście. Każdy element wskazuje na poprzednika i następcę, a jeśli takiego nie ma to jest NULL. Dodatkowo lista ma zadeklarowany element pierwszy i ostatni.

Zaraz będę kombinował z poradami z Waszych postów, ale tak średnio zrozumiałem, co jest nie halo :/

Wymyśliłem sobie koncepcję przesuwania się od początku do końca i usuwania każdego elementu po kolei... Ale teraz chciałbym się dowiedzieć - jeśli usuwam przez delete element zadeklarowany j.w. to czy zwalniana jest także jego wartość (mam na myśli część klasy element).

ADD:

Andy Misio napisał(a):

Jeżeli nie jesteś pewny czy w argumencie do delete jest prawidłowy wskaźnik to ustaw go po każdym delete na NULL.
Czyli zamiast "delete wsk;" uzywaj "delete wsk;wsk = NULL;". O ile podwójne zwolnienie pamieci nie wróży niczego dobrego o tyle zwolnienie NULL'a nic nie powoduje.
Co do kodu warto zastanowić się co się tam dzieje

if ((pierwszy!=NULL)&&(ostatni!=NULL)){ 
    element *pom, *pom2;
    pom=pierwszy;
    for (int i=0;pom->nastepny!=NULL;){
        pom2=pom;							//kopia wskaznika
        pom=pom2->nastepny;					//kopia wskaznika nastepny, puki jeszcze istnieje
        delete pom2;							//usun z pamieci
    }
//ostatnia interacja, pozostal Ci ogon, a więc zwalniasz go i tylko go
    delete pom;								//lub     delete ostatni;
}
 

Przerobiłem - faktycznie taki kod ma więcej sensu. Niestety to nie rozwiązało problemu. Widocznie gdzieś w innym miejscu zostają mi zmienne.

W jaki sposób mogę to wykryć? Jakiego narzędzia użyć?

ADD2:

A być może cieknie przy samym dodawaniu, chociaż sam już nie wiem, oto kod:

 double lista::dodaj(int indeks, int unsigned wartosc){ 
	//metoda dodająca podaną przez użytkownika wartość na podany przez użytkownika indeks
	element * ten = new element;
	element * pom = new element;
	pom=pierwszy;
	Czas.start();
	if ((ostatni==NULL)&&(pierwszy==NULL)){
		//jeśli jest to pierwszy element
		ten->wartosc=wartosc;
		ten->nastepny=NULL;
		ten->poprzedni=NULL;
		ostatni = ten;
		pierwszy = ten;
	}else{
		for (int i=0;i<indeks-1;i++){
			if ((pom->nastepny==NULL)&&(indeks-i>1)){
				//sprawdzenie, czy nie indeks nie jest poza zakresem
				cout<<indeks<<"-"<<i<<"="<<indeks-i<<endl;
				cout<<"BLAD - indeks poza zakresem"<<endl;
				delete ten;
				delete pom;
				Czas.stop();
				return Czas.wynik();
			}

			pom=pom->nastepny;

		}
		if (indeks==0){
			ten->wartosc=wartosc;
			ten->nastepny=pierwszy;
			ten->poprzedni=NULL;
			pierwszy->poprzedni=ten;
			pierwszy=ten;
		}
		else if (pom->nastepny==NULL){
			//jeśli jest to ostatni element
			ten->wartosc=wartosc;
			ten->poprzedni=ostatni;
			ten->nastepny=NULL;
			ostatni->nastepny=ten;
			ostatni=ten;
		}
		else{
			//dodanie w środku listy
			ten->wartosc= wartosc;
			ten->poprzedni = pom;
			ten->nastepny = pom->nastepny;
			pom->nastepny->poprzedni=ten;
			pom->nastepny=ten;	
		}
	}
	delete ten;
	delete pom;
	Czas.stop();
	return Czas.wynik();
}
0

Jak masz czas i chęć to możesz przeładować operatory new, delete i sprawdzić gdzie pamięć nie została zwolniona. Jednak szybciej wyniki uzyskasz jakimś toolem.
https://blogs.msdn.microsoft.com/calvin_hsia/2009/01/19/overload-operator-new-to-detect-memory-leaks/
https://wiki.qt.io/Profiling_and_Memory_Checking_Tools

0

problemem jest, że strasznie to komplikujesz i za dużo pakujesz do jednej funkcji. Po co tyle new i delete? Twórz tylko to co jest potrzebne:

lista::element* lista::znajdzElementOIndeksie(int indeks) {
      element *wynik = pierwszy;
      while(wynik && indeks>0) {
            wynik = wynik->nastepny;
            --indeks;
      }
      if (indeks==0)
            return wynik;
      throw std::out_of_range("indeks poza zakresem listy");
}

void lista::dodaj(int indeks, int unsigned wartosc) {
     element *dodajPrzed = znajdzElementOIndeksie(indeks);
     element *nowy = new element;
     nowy->wartosc = wartosc;

     nowy->nastepny = dodajPrzed;
     if (dodajPrzed)
     {
           nowy->poprzedni = dodajPrzed->poprzedni;
     } else {
           nowy->poprzedni = ostatni;
     }

     if (nowy->poprzedni) {
           nowy->poprzedni->nastepny = nowy;
     } else {
          pierwszy = nowy;
     }

     if (nowy->nastepny) {
          nowy->nastepny->poprzedni = nowy
     } else {
          ostatni = nowy;
     }
}
0
void lista::znajdzElementOIndeksie(int indeks) {
      element *wynik = pierwszy;
      while(wynik && indeks>0) {
            wynik = wynik->nastepny;
            --indeks;
      }
      if (indeks==0)
            return wynik;
      throw std::out_of_range("indeks poza zakresem listy");
}

void lista::dodaj(int indeks, int unsigned wartosc) {
     element *dodajPrzed = znajdzElementOIndeksie(indeks);
     element *nowy = new element;
     nowy->wartosc = wartosc;

     nowy->nastepny = dodajPrzed;
     if (dodajPrzed)
     {
           nowy->poprzedni = dodajPrzed->poprzedni;
     } else {
           nowy->poprzedni = ostatni;
     }

     if (nowy->poprzedni) {
           nowy->poprzedni->nastepny = nowy;
     } else {
          pierwszy = nowy;
     }

     if (nowy->nastepny) {
          nowy->nastepny->poprzedni = nowy
     } else {
          ostatni = nowy;
     }
}
</quote>

Spróbowałem to zaimplementować, ale w pierwszej funkcji zwracasz zmienną, więc kompilator wykrył, że void to nie bardzo pasuje. Spróbowałem to zmienić na

element* lista::znajdzElementOIndeksie(int indeks)

ale wywala mi błąd "element" does not name a type, mimo że klasa element jest zaimplementowana wcześniej niż ta funkcja :/ Jak mam to rozumieć?

ADD:

Oto całość kodu, a przynajmniej ta potrzebna część (bez działań na innych elementach).

menu.cpp -http://pastebin.com/n46Gkkkg
lista.h / lista.cpp - http://pastebin.com/rLng13kQ
czas.h / czas.cpp - http://pastebin.com/D1p9XtAQ

0

lista.cpp

  1. 98 linijka "element * ten = new element;" zamień na "element * ten;".
    Nie twórz nowego objektu do przypisań wskaźnikowych.
  2. 161 linijka, to samo co wyżej.

Metoda dodaj:


void lista::dodaj(int wartosc,unsigned int indeks){
        element *tmp = pierwszy;

        if(!tmp && indeks){
                        cout << "indeks poza zakresem" << endl;
                        return;
        }else if(tmp){
                element *tmp2;
                do{
                        --indeks;
                        if(indeks < 0){
                                cout << "indeks poza zakresem" << endl;
                                return;
                        }
                        tmp2 = tmp;
                        tmp = tmp->nastepny;
                }while(tmp);
                tmp = tmp2;
        }
        element *add = new element;
        add->wartosc = wartosc;
        if(!pierwszy){
                pierwszy = ostatni = add;
        }else if(tmp == pierwszy){
                pierwszy->poprzedni = add;
                add->nastepny = pierwszy;
                pierwszy = add;
        }else if(tmp->nastepny){
                add->nastepny = tmp->nastepny;
                tmp->nastepny->poprzedni = add;

                add->poprzedni = tmp;
                tmp->nastepny = add;
        }else{
                add->poprzedni = ostatni;
                ostatni = ostatni->nastepny = add;
        }
}

 

metoda czysc:

void lista::czysc(void){
        if(pierwszy){
                element *tmp = pierwszy;
                element *tmp2;
                while(tmp->nastepny){
                        tmp2 = tmp;
                        tmp = tmp->nastepny;
                        delete tmp2;
                }
                delete tmp;
        }
        pierwszy = ostatni = NULL;
}
 

Mam nadzieję, że jest to dla Ciebie w miarę jasne, jak coś pytaj. Czyścisz po skończonej pracy na liście.
To tak na szybko.
Błędy masz jeszcze w metodzie usun, nie ma tam delete.
Są jeszcze dwa błędy w metodzie wyswietl, nie sprawdzasz czy jest coś do wyświetlenia.

0

Mam nadzieję, że Wasze święta były udane :-)

AndyMisio rozumiem, chodzi o to, że element tworzyć tylko kiedy będzie on faktycznym, nowym elementem z wartością, a dla wskazania istniejącego elementu nie trzeba tworzyć całego obiektu. Kurcze, takie logiczne a jednak o tym nie pomyślałem.

Przy wyświetlaniu jest taki numer, że program wcześniej sprawdza, czy lista jest pusta, a dopiero potem wykonuje (lub nie) metodę "wyswietl".

Odpaliłem program z tworzeniem plików i dodawaniem elementów i jest super, RAM już tak nie ucieka.

Mam teraz pytanie bardziej na zasadzie "Jak działa program w systemie". Teraz jest tak, że to, co napisałem zajmuje koło 11MB, ale rośnie - co prawda baaardzo powoli, ale jednak (Po przerobieniu około 3000plików o jakiś 3MB).
W tym wypadku mam pytanie, czy jest to kwestia jakiegoś innego wycieku pamięci, czy tak to prostu jest (działam na Okienkach).

Raczej nie będę już tego łatał, ale pytam, bo jestem ciekawy, co może być powodem.

0

Masz wyciek w metodzie "usun".
Nie wiem z jakiego gui korzystasz i ile widgetków w nim tworzysz.
Jeżeli nie jest wymagane korzystanie z własnego rozwiązania, użyj std::list, tak jak wyżej wspomniał @kq.
Wszystkie problemy powinny się skończyć, do tego kod się uprości (zaoszczędzisz kilkadziesiąt linijek).

1 użytkowników online, w tym zalogowanych: 0, gości: 1