Wyciek pamięci

Z Wikipedii, wolnej encyklopedii
Skocz do: nawigacji, wyszukiwania

Wyciek pamięci (ang. memory leak) — jest szczególnym rodzajem niezamierzonego użycia pamięci przez program komputerowy, gdy program nie zwalnia pamięci gdy nie jest już potrzebna, a rezerwuje nową. Efekt jest zwykle wynikiem błędu w programie.

Wycieki pamięci są efektem bardzo niepożądanym. Program bowiem zajmuje coraz więcej pamięci, ale nie jest w stanie jej wykorzystać ani zwolnić. Szczególnie w aplikacjach, które działają przez długi czas (w większości serwerowych), efekt wycieku pamięci stopniowo narasta. Sam wyciek prowadzi do spadku wydajności, w skrajnym przypadku zawieszenia się programu, a nawet zablokowania całego systemu. Doprowadzenie w wadliwym programie do możliwie dużego wycieku pamięci może być jednym ze sposobów wykonania ataku DoS.

Kod programu, który powoduje wycieki pamięci, jest kodem błędnym.

Przykłady wycieków pamięci[edytuj | edytuj kod]

C++[edytuj | edytuj kod]

int fun() {
  int* wsk = new int; // zarezerwowane pamięci na liczbę typu int
  *wsk = 20; // wpisanie w zarezerwowane miejsce wartości
  return *wsk;
}

W powyższym przykładzie wykonanie funkcji fun spowoduje rezerwację obszaru pamięci na jedną liczbę typu int i zapisanie w nim wartości 20. Wartość ta jest zwracana przez funkcję, jednak obszar pamięci o adresie wskazywanym przez wskaźnik wsk nie zostaje zwolniony (brak wywołania instrukcji delete). Oznacza to wyciek pamięci – każde kolejne wywołanie funkcji fun powiększy rozmiar tego wycieku co najmniej o rozmiar typu danych int.

Java, C#[edytuj | edytuj kod]

W takich językach programowania jak Java czy C# problem wycieków pamięci został częściowo zniwelowany poprzez zastosowanie odśmiecania pamięci lub inaczej śmieciarza (ang. Garbage Collector). Garbage Collector nie rozwiązuje jednak całkowicie problemu z wyciekami pamięci, ponieważ zwalnia tylko pamięć, do której nie istnieją odwołania. Powoduje to, że niestarannie napisany kod może skutkować, iż śmieciarz nie zwolni pewnego obszaru pamięci, mimo że nie zamierzamy go używać.

JavaScript[edytuj | edytuj kod]

Pomimo tego, że JavaScript jest językiem wysokopoziomowym i nie udostępnia metod alokacji i zwalniania pamięci, możliwe jest spowodowanie wycieku pamięci wskutek niedoskonałości działań interpretera bądź środowiska uruchomieniowego. Przykładowo, w przeglądarce Internet Explorer, działają równolegle dwa niezależne systemy odśmiecania pamięci - jeden w warstwie DOM, zaś drugi w przestrzeni JScript. W konsekwencji utworzenie referencji cyklicznej pomiędzy takimi elementami (np. poprzez przypisanie metod obsługi zdarzeń) może skutkować wyciekiem pamięci.

Obsługa wyjątków a wycieki pamięci[edytuj | edytuj kod]

Częstą sytuacją podczas działania programu jest zaalokowanie pewnego obszaru pamięci, pracy z nim a następnie zwolnienie go. W C++ taka funkcja mogłaby mieć postać (pominięto definicje klas i funkcji - nie są potrzebne w przykładzie):

 class KlasaZasobu {
   // definicja klasy
 };
 
 void pracaZzasobem( KlasaZasobu* dana ) {
 //   implementacja funkcji
 }
 
 void funkcja() { 
   KlasaZasobu* zasob = new KlasaZasobu(); // 1
   pracaZzasobem( zasob );                 // 2
   delete zasob;                           // 3
 }

W (1) następuje alokowanie pamięci poprzez stworzenie obiektu KlasaZasobu. Następnie zostaje wywołana pewna funkcja pracaZzasobem( KlasaZasobu* ) (2) pracująca z obiektem klasy KlasaZasobu. Po tym wszystkim nastąpi zwolnienie zarezerwowanego obszaru pamięci w (3). Na pierwszy rzut oka, funkcja ta nie powoduje wycieków pamięci. Wyobraźmy sobie sytuację w której po alokowaniu pamięci w (1), funkcja pracaZzasobem( KlasaZasobu* ) wyrzuci wyjątek (np. z powodu błędnych danych). Zostaną usunięte wszystkie obiekty lokalne i wykonanie programu zostanie przekazane do miejsca gdzie została wywołana funkcja funkcja() - zgodnie z działaniem mechanizmu wyjątków. Lokalnym obiektem w tym przypadku jest wskaźnik zasob i zostanie on usunięty, jednak zarezerwowany obszar pamięci do którego ma dostęp już nie. Z powodu wyrzucenia wyjątku przez funkcję pracaZzasobem( KlasaZasobu* ), zasób nie zostanie zwolniony w (3). W efekcie powstanie wyciek pamięci.

Aby zaradzić takiej sytuacji i obronić się przed wyciekiem pamięci stosuje się dodatkową klasę, w której w konstruktorze przypisuje się alokowany wskaźnik, a w destruktorze zwalnia się alokowany obszar pamięci:

  1.  class KlasaZasobu {
    
  2.    // definicja klasy
    
  3.  };
    
  4.  
    
  5.  void pracaZzasobem( KlasaZasobu* dana ) {
    
  6.  //   implementacja funkcji
    
  7.  }
    
  8.  
    
  9.  class KlasaPomocnicza {
    
  10.    public:
    
  11.      KlasaPomocnicza( KlasaZasobu* wskaznik ) {
    
  12.        this->dana = wskaznik;
    
  13.      }
    
  14.      KlasaZasobu* pobierzWskaznik() {
    
  15.        return this->dana;
    
  16.      }
    
  17.      ~KlasaPomocnicza() {
    
  18.        delete this->dana;
    
  19.      }
    
  20.   private:
    
  21.     KlasaZasobu* dana;
    
  22.  };
    
  23.  
    
  24.  void funkcja() { 
    
  25.    KlasaPomocnicza zasob( new KlasaZasobu() ); // 1
    
  26.    pracaZzasobem( zasob.pobierzWskaznik() );   // 2
    
  27.  }
    

W (1) następuje stworzenie lokalnego obiektu klasy KlasaPomocnicza i podanie w konstruktorze wskaźnika do nowo alokowanego obszaru pamięci. Konstruktor ten przypisuje polu dana wskaźnik nowo alokowanego obszaru. Następnie zostaje wywołana funkcja pracaZzasobem( KlasaZasobu* ) której jako parametr podajemy wskaźnik korzystając z funkcji w klasie KlasaPomocnicza. Gdy funkcja pracaZzasobem( KlasaZsobu* ) wyrzuci wyjątek zostaną usunięte wszystkie obiekty lokalne. W tym wypadku obiektem lokalnym jest obiekt zasob dla którego zostanie wywołany jego destruktor w którym nastąpi zwolnienie alokowanego zasobu. Funkcja nie będzie mieć również wycieku pamięci w momencie gdy funkcja pracaZzasobem( KlasaZasobu* ) nie wyrzuci wyjątku ponieważ również skończy się zasięg ważności obiektu zasob i zostanie on również usunięty (a wraz z nim zwolniony zostanie alokowany obszar pamięci).

Programista nie musi pisać takiej klasy pomocniczej dla klasy, której obiekt alokuje używając operatora new. Z pomocą przychodzą szablony klas. W standardowej bibliotece klas jest to szablon auto_ptr. Dostępne są również inne implementacje. Najpopularniejszą są szablony z biblioteki Boost: scoped_ptr (dla prostego zwalniania obiektów w danym zasięgu ważności) oraz shared_ptr (dla automatycznego zwalniania współdzielonych zasobów poza zasięgiem ważności).

Problem wycieków pamięci w związku z obsługą wyjątków dotyczy głównie C++. W innych językach problem ten nie występuje ze względu na brak mechanizmu wyjątków lub inną politykę zarządzania pamięcią (Garbage collection).

Zobacz też[edytuj | edytuj kod]