Skocz do zawartości
  • 👋 Witaj na MPCForum!

    Przeglądasz forum jako gość, co oznacza, że wiele świetnych funkcji jest jeszcze przed Tobą! 😎

    • Pełny dostęp do działów i ukrytych treści
    • Możliwość pisania i odpowiadania w tematach
    • System prywatnych wiadomości
    • Zbieranie reputacji i rozwijanie swojego profilu
    • Członkostwo w jednej z największych społeczności graczy

    👉 Dołączenie zajmie Ci mniej niż minutę – a zyskasz znacznie więcej!

    Zarejestruj się teraz

Rekomendowane odpowiedzi

Opublikowano

Dynamiczna pamięć

 

W klasycznym języku C istniały funkcje, takie jak malloc czy free, które służyły do rezerwacji pamięci już w czasie działania programu. Ich wadą było jednak to, że nie sprawdzały one zgodności typów. Trzeba też było się znać na modelach pamięci. W C++ sytuacja wygląda dużo lepiej! Język ten został wyposażony w specjalne operatory do przydzielania i zwalniania pamięci, które nie mają wspomnianych wad. Operatory te to new i delete. Najpierw zajmiemy się tym pierwszym.

 

Operator new rezerwuje dla nas miejsce w zasobach pamięci. W przeciwieństwie do zwykłych zmiennych, taki obszar nie ma nazwy. Operator new daje nam tylko adres tego obszaru, który jest jedynym sposobem dobrania się do tej pamięci. Musimy sobie ten adres zapamiętać i dobrze pilnować, inaczej stracimy dostęp do przydzielonego nam kawałka pamięci. Do tego celu najlepiej nadaje się wskaźnik. Looknij sobie na poniższy kod:

 

 

 

int * wskaznik; //Ten wskaźnik pokaże na przydzieloną nam pamięć

wskaznik = new int; //Przydzielenie pamięci dla zmiennej typu int

 

 

 

 

Jak widzisz, najpierw deklarujemy wskaźnik, w którym zapamiętamy za chwilę adres przydzielonego nam kawałka pamięci. Wskaźnik ten służy do pokazywania na typ int. W następnej linijce używamy operatora new, podając mu typ zmiennej, dla jakiej chcemy przydzielić pamięć. Operator new przydziela nam 4 bajty z zapasu pamięci [bo tyle potrzeba na zmienną typu int] i zwraca nam adres tego obszaru, który my zapamiętujemy sobie w zmiennej wskaznik. Oczywiście możemy to zrobić w jednej linijce:

 

 

 

int * wskaznik = new int;

 

 

 

 

Prawda, że to dziecinnie proste? ;-) Właśnie dostaliśmy "przydział na mieszkanie", czas się więc wprowadzić! ;-) Zobacz, w jaki sposób możemy korzystać z przydzielonej pamięci:

 

 

 

*wskaznik = 30; //Zapisanie liczby typu int w pamięci

cout << "Nowym mieszkańcem jest liczba " << *wskaznik << endl;

 

 

 

 

Pierwsza linijka posłuży się wskaźnikiem, by umieścić w pamięci liczbę 30. Druga linijka spowoduje pobranie zawartości tej pamięci i wypisanie jej na ekranie. Oczywiście liczba typu int została tu użyta jako przykład. Możesz przydzielać pamięć także dla zmiennych innych typów, w tym także typów zdefiniowanych przez siebie. Jest jednak jedna zasada: wypożyczoną pamięć trzeba zwrócić systemowi, gdy już jej nie potrzebujemy. Inaczej pamięci może szybko zabraknąć, nawet dla nas. Do zwalniania przydzielonej pamięci służy operator delete. Pracuje on na wskaźniku, który uprzednio dostaliśmy od operatora new. Poniżej masz przykład jego użycia:

 

 

 

delete wskaznik; //Zwolnienie pamięci, na którą pokazuje wskaźnik

 

 

 

 

I tu się kryje pewna pułapka. Zgadnij, co się stanie, jeśli przez przypadek przestawisz wskaźnik na jakiś inny adres? Po pierwsze, tracisz na zawsze dostęp do przydzielonego kawałka pamięci [chyba że znasz sposób, żeby ustawić go w to samo miejsce ;-)]. Powoduje to tak zwany wyciek w pamięci. Niezwolniona pamięć wciąż jest oznaczona jako zajęta i nikt inny [nawet my] nie może już z niej skorzystać, co objawia się tym, że pamięci zaczyna powoli brakować. Druga pułapka jest nawet groźniejsza! Jeśli użyjesz na takim zmienionym wskaźniku operatora delete, program najzwyczajniej w świecie wysypie się! Musisz bardzo uważać, by nie dopuszczać do takich sytuacji. Staraj się nie przestawiać wskaźników ustawionych operatorem new, a po zwolnieniu pamięci operatorem delete ustawiaj taki wskaźnik na NULL, czyli adres zerowy. Wywołanie operatora delete dla takiego wskaźnika niczym nie grozi.

 

Kiedyś powiedziałem ci, że podczas deklarowania tablicy, jej rozmiar musi być już znany w czasie kompilacji. Nie było możliwości, by zrobić tablicę o takim wymiarze, jaki zażyczy sobie użytkownik w trakcie działania programu. Dziś poznałeś narzędzie, które może nam na to pozwolić. Napiszemy teraz program, który utworzy w pamięci tablicę liczb typu int. Rozmiar tej tablicy będzie podawał użytkownik na początku programu. Następnie poprosimy go o wpisanie do niej odpowiedniej ilości liczb i wyświetlimy je na ekranie. Na koniec oczywiście zwolnimy pamięć, żeby nie robić bydła. ;-P

 

 

 

#include <iostream.h>

int* Tablica; //Wskaźnik na dynamicznie przydzieloną tablicę

int ile; //Rozmiar tablicy

 

int main() {

cout << "Jak duza ma byc tablica?\n"; cin >> ile;

Tablica = new int[ile];

for (int n=0; n<ile; n++) {

cout << "Podaj element numer " << n; cin >> Tablica[n];

}

cout << endl << "Zawartosc tablicy:\n";

for (int n=0; n<ile; n++) cout << Tablica[n] << ",";

delete [] Tablica;

}

 

 

 

 

Już wyjaśniam! ;-) Najpierw deklarujemy wskaźnik na typ int, który pokaże na naszą tablicę. W zmiennej ile będziemy trzymać rozmiar tablicy, który podał użytkownik. Wewnątrz funkcji main widzimy zapytanie użytkownika o rozmiar tablicy i pobranie jego odpowiedzi do zmiennej ile. Kolejna linijka może sprawić ci nieco kłopotu, bo jest dla ciebie nowością. Otóż jest to konstrukcja z operatorem new, która zamiast dla jednej zmiennej typu int, przydziela hurtem miejsce dla kilku kolejnych takich zmiennych. O tym, ile ich będzie, decyduje wartość w nawiasach kwadratowych. Przypomina to trochę deklarację tablicy i rzeczywiście taka tablica jest tworzona, lecz tym razem dynamicznie, w zapasie pamięci. Operator new zwraca wskaźnik na jej pierwszy element. Dalej mamy pętlę, w której pytamy użytkownika o wartości kolejnych elementów tablicy. Jak widzisz, użyliśmy wskaźnika tak, jakby był on nazwą tablicy, ale to już chyba znasz ;-). Gdy już tablica zostanie wypełniona, kolejna pętla wyświetla jej zawartość. Na koniec oczywiście zwalniamy tablicę, oddając pamięć systemowi. Wyjaśnienia wymagają jeszcze te nawiasy kwadratowe obok delete. Otóż operator ten ma jakby dwie wersje. Gdybyśmy pominęli nawiasy, operator uzna że chcesz zwolnić tylko jeden element typu int [bo takiego typu jest wskaźnik]. Nie jest on w stanie sprawdzić, że tam stoi jeszcze kilka takich elementów. Jeśli jednak użyjemy tych nawiasów, operator zwolni już tablicę w calości, bez żadnych wpadek. Żeby nie robić chlewu, dla tablic musisz zawsze używać wersji z nawiasami.

 

Wiadomo, pamięć nie jest z gumy. ;-) Jak dużo pamięci możemy "wypożyczyć" od systemu? Oj, baaardzo dużo! :-D Czasy DOSa, kiedy to pamięć była ograniczona do marnych 640 kilobajtów, dawno już minęły ;-). Pod systemami takimi jak Windows czy Linux możesz bez obaw rezerwować nawet setki megabajtów pamięci, bo nawet jeśli braknie pamięci RAM, system może zrzucić część pamięci na dysk, zwalniając w ten sposób nowe zasoby :-) Nazywa się to stronicowaniem [ang. swapping], lub pamięcią wirtualną.

 

Jeśli jednak uwielbiasz wypełniać pamięć swoim badziewiem, prędzej czy później musi dojść do sytuacji, że pamięć się skończy. Jak to rozpoznać? Najprościej jest posłużyć się faktem, że gdy operator new z jakichś powodów nie może przydzielić więcej pamięci, zwraca pusty wskaźnik [czyli NULL]. Wystarczy, jeśli przed użyciem sprawdzimy ten wskaźnik, czy nie równa się przypadkiem NULL. Właściwie moglibyśmy to zrobić tak:

 

 

 

if (!wskaznik) cout << "Zabraklo pamieci!";

 

 

 

 

bo NULL to adres o wartości 0. Jednak spotkałem się z kompilatorem firmy Borland, w którym wskaźnik NULL nie pokazuje na adres zerowy, lecz na adres 0xFFFFFFFF!!! :-/ Dlatego najbezpieczniej jest jednak porównać go z wartością NULL.

 

No, to narazie tyle. Właściwie powinienem napisać jeszcze, że operator new generuje tak zwany wyjątek, gdy coś pójdzie nie tak, ale nie znasz jeszcze wyjątków więc nie będę ci mącił w głowie ;-D Może kiedyś jeszcze dopiszę tu, jak ustawić własną obsługę braku pamięci, ale narazie powinno ci wystarczyć to co jest. Zapoznaj się dokładnie z tą lekcją, bo będziemy z niej często korzystać w przyszłości.

×
×
  • Dodaj nową pozycję...