Proces tworzenia zmiennej w pamięci jest bardziej złożony już się wydaje, nie wystarczy napisać typu i nazwy. My tak robimy, bo kompilator wszystko robi za nas i nie musimy się o nic innego troszczyć. Jednak pokażę sposób na dynamiczne zarządzanie pamięcią w naszym programie. Radzę unikać tego sposobu, jeżeli nie musimy go stosować. Kompilator może wszystko za nas zrobić. Używajmy go wtedy, gdy tylko musimy. Czasem przez pomyłkę, ten sposób, program może zeżreć nam sporo pamięci w komputerze.

Po pierwsze, aby móc bawić się pamięcią, będziemy potrzebowali wskaźnika. Jak wiadomo wskaźnik to adres w pamięci, więc bez niego nie będziemy mogli odwoływać się do poszczególnym fragmentów w pamięci.

...
int* wskaz;
...

Mając wskaźnik, możemy przydzielić do niego nowy obszar pamięci. Pamięć komputera składa się z fragmentów, które są zarezerwowane przez jakiś program, bądź adresów, które nie są zarezerwowane i nie służą żadnemu programowi. W lekcji o wskaźnikach wskaźnikom przypisywaliśmy adres jakiejś zmiennej z naszego programu, więc przypisywaliśmy adres pamięci, który już jest zajęty przez tą zmienną(używa go do przechowywania wartości), teraz pokażę, jak wskaźnikowi przydzielić adres pamięci, który nie jest zarezerwowany przez żaden program, innymi słowy nasz program zajmie nowy fragment pamięci, dodamy do naszego programu fragment nowej pamięci, nową zmienną:

...
wskaz=new int;
...

Tłumacząc to, wskaźnikowi wskaz przypiszemy nowy fragment pamięci, wcześniej nieużywany przez żaden program, a ten fragment pamięci, będzie mógł przechowywać wartość zmiennej int. Chyba widzisz, że w ten sposób dodaliśmy do naszego programu nową zmienną typu int. Nie ma ona nazwy, ale za to mamy wskaźnik, który wskazuje na jej zawartość.

Tak, więc mamy nową dynamicznie utworzoną zmienną, przypiszmy jej jakąś wartość:

...
*wskaz=
123
;
...

Przypisujemy, w zwyczajny sposób, czyli to, na co wskazuje wskaźnik wskaz, przypisz wartość 123.

Zanim wyjdziemy z programu, musimy usunąć taką zmienną, skoro sami ręcznie ja utworzyliśmy, to sami musimy zadbać o zwolnienie tego fragmentu pamięci i odznaczenie go jako nieużywany przez żaden program. Jeżeli teraz wyszlibyśmy z programu, to fragment ten nie zostałby zwolniony i byśmy stracili możliwość dobrania się do niego.

...
delete wskaz;
...

Pamięć zwalniamy używając operatora delete, który odznaczy ten fragment pamięci jako nieużywany, w ten sposób jakiś inny program potem może wykorzystywać ten fragment, a my będziemy mieli więcej wolnej pamięci w komputerze, jak myślisz co by się stało jakby programy rezerwowałyby pamięć i jej już nie oddawały? Chyba oczywiste, że by jej szybko zabrakło.

Warto także dodać, że sam operator delete nie modyfikuje wartoci wskaźnika, dlatego po operacji odznaczenia pamięci, dobrze wyzerować wartoć wskaźnika, gdyż nadal będzie wskazywał na ten odznaczony już, fragment pamięci. Tylko nie zerujmy go przed operatorem delete!

CO GDY PAMIĘĆ SIĘ SKOŃCZY?

Dziś komputery mają sporo pamięci, ciężko by było zając całą pamięć samymi "intami". Na dodatek dzisiejsze systemy posiadają systemy, które sterują pamięcią, gdy zaczyna brakować pamięci lub dany fragment jest długo nieużywany, system przenosi go na dysk, oszczędzając w ten sposób pamięć(np.. plik stronicowania w Windowsie, partycja SWAP w Linuxie). Jednak, gdy programy nie oszczędzają pamięci szybko może zmniejszyć się ilość wolnego miejsca w pamięci. Istnieje możliwość, że operator "new", nie będzie mógł przydzielić fragmentu pamięci, co się wtedy dzieje? Zwraca wartość zerową. Możemy zabezpieczyć program na taki wypadek:

...
char* wskaz=new char;
if(wskaz==
0
)
std::cout<<
"Wystapil problem z przydzieleniem pamieci!\n"
;
...

DYNAMICZNE TABLICE

Tworzenie pojedynczej zmiennej poprzez wskaźnik, jest mało praktyczne, bo po co skoro możemy taką zmienną stworzyć w normalny sposób. Natomiast dynamiczne przydziały zmiennych są już wykorzystywane przy większych złożonych elementach języka, takich jak tablice.

Jak będziemy programować, spotkacie się z problemem, że będziemy musieli utworzyć jakąś tablicę i w momencie pisania progamu nie będziemy znali jej rozmiaru, np. rozmiar ma podać użytkowanik. W takim przypadku musimy użyć tablicy dynamicznej, która zostanie utworzona dopiero podczas działania programu. Popratrzmy na przykład:

...
int* wskaznik;
//tworzymy zwykły wskaźnik na zmienną typu int(taką chcemy mieć tablicę)

unsigned int rozmiar=
46
;
//zmienna przechowująca rozmiar naszej tablicy

wskaznik=new int[rozmiar];
wskaznik[
0
]=
35
;
wskaznik[
1
]=
105
;
delete[] wskaznik;
...

Tworzymy wskaźnik, następnie przydzielami temu wksaźnikowi nowy fragment pamięci, lecz tym razem przy operatorze używamy także [], co oznacza, że tworzymy tablicę tylu elementów, ile podamy w nawiasiku []. Teraz zauważ, że używamy wskaźnika tak jak zwykłej tablicy, dlatego, że tablica to właśnie wskaźnik na pierwszy element. Na koniec musimy zwolnić naszą tablicę, jako, że utworzyliśmy tablicę(więcej niż 1 element) musimy użyć także nawiasika [] przy operatorze delete, choć teraz już nie musimy podawać rozmiaru, kompilator sam ustali sobie rozmiar.

DYNAMICZNE STRUKTURY

Dynamicznie możemy także tworzyć struktury(właściwie to wszystko możemy robić dynamicznie). W tym wypadku tworzymy strukturę jak zmienną. A do pól struktury odwołujemy się poprzez wcześniej poznany operator ->.

...
struct struktura
{
  int pole;
};

struktura* wskaznik;
wskaznik=new struktura;
wskaznik->pole=
35
;
delete wskaznik;
...

KILKA SŁÓW O PRACY Z DYNAMICZNYMI PRZYDZIAŁAMI PAMIĘCI

Pierwszą rzeczą, jaką trzeba w tym temacie omówić jest kwestia tzw. "Access Violation". Są to błędy spowodowane niepoprawnym dostępnem do pamięci. Od Windows 2000 stosuje się nowy schemat dostępu do pamięci. Otóż każda aplikacja ma swój obszar pamięci, w którym ma zmienne. A odwoływanie się do cudzego(obszaru innej aplikacji) obszaru, spowoduje błąd dostępu do pamięci, tzw. "Access Violation". W przypadku wystąpienia takiego błędu system zatrzymuje proces i wyświetla błąd. W różnych wersjach systemów są różne komunikaty, ale zawsze będziemy mieli w treści komunikatu podane do jakiego adresu aplikacja chciała się odwołać. Tak samo ma się sytuacja, gdy chcemy odwołać się do adresu 0, czyli pustego wskaźnika, który nie wskazuje na nic. Spowodujmy taki zamierzony ;-) błąd, żeby było wiadomo o co chodzi, spróbujmy przypisać coś poprzez wskaźnik, który nie będzie wskazywał żadnej zmiennej, jego wartość będzie wynosiła 0.

#include <iostream>


int main()
{
  int* wskaz=
0
;
//pusty wskaźnik, nie wskazuje na żadną zmienną

  *wskaz=
0
;
//tutaj próbujemy przypisać zmiennej na którą wskazuje wskaźnik wartość 0, wustąpi błąd bo wskaźnik wynosi 0

}

Jestem pewny, że po skompilowaniu tego programu zobaczyliście błąd dostępu do pamięci. W komunikacie powinno być podane, że odwoływaliśmy się do pamięci pod adresem 0x0.

Jak się zabezpieczać, przed tego typu błędami? No najważniejsze to nie popełniać błędów w przydzielaniu odpowiednich wartości wskaźnikom. Poza tym, zawsze dobrze sprawdzić przed odwołaniem się do wskaźnika czy wskaźnik ma jakąś wartość, czy przypadkiem nie wynosi 0 i w razie czego odpowiednio zareagować na ten błąd w programie. Trzecią sprawą jest operator delete. Powiedzmy, że mieliśmy wskaźnik, który wskazywał na zmienną o adresie 0x34. Zwolniliśmy tą zmienną operatorem delete. Ale operator delete nie modyfikuje wartości wskaźnika, jedyne co robi to odznacza fragment pamięci na który wskazywał wskaźnik, jako nieużywany, innymi słowy zmienna pod tym wskaźnikiem przestaje istnieć. Ale w naszym programie wskaźnik nadal pokazuje na nieistniejący już w naszym programie adres 0x34, a takie odwołanie się pod taki zwolniony adres zakończy się Access Violationem :-) Dlatego, aby temu zapobiedz po użyciu operatora delete wypada wyzerować wartość wskaźnikia, tak aby program wiedział, że wskaźnik nie wskazuje na żadną zmienną.

Wejść na stronę:     Ilość osób online:
Prawa autorskie © 2008 crayze. Wszelkie prawa zastrzeżone.
crayze7@gmail.com