background image

Pascal

Wskaźniki, procedury 

dynamicznego 

przydziału pamięci

background image

Wskaźniki

• Wskaźniki to adresy pamięci, 

które pokazują komputerowi 
miejsce gdzie znajdują się 
jakieś dane. Możesz 
przykładowo przechowywać 
w pamięci wskaźnik na 
obrazek bitmapowy, podczas 
gdy przechowywanie obrazu 
w tablicy byłoby niezbyt 
praktyczne. Pamięć do której 
odnoszą się wskaźniki można 
w każdej chwili zwalniać, 
deklarować i zmieniać.

background image

Budowa wskaźników*

• Ze względu na ryzoko zawieszenia programu wskaźniki są 

traktowane jako typy, które należy umiejętnie stosować. 
Wskaźniki na ogół zbudowane są z 4 bajtów. Pierwsze 2 
określają segment danych, kolejne 2 ofset.

Pamięć konwencjonalna (pierwszy 1MB pamięci 
komputera) podzielony jest na segmenty po 16KB każdy. 
Tak więc 1 segment ma adres 0, drugi 1, trzeci 2, itd... 
Ofset to przemieszczenie względem segmentu. Dzięki 
ofsetowi możemy dojść do każdego jednego bajtu pamięci. 
Adres typu pointer składa się z dwóch bajtów segmentu, 
dwóch ofsetu i jest najczęściej zapisywany w kodzie 
szesnastkowym, dzięki czemu jest bardziej czytelny, np.

 $a000:00, $b800:00

background image

Typ  pointer

• Typ wskaźników nazywa się 

pointer. Gdy jakąś zmienną 
zadeklarujesz jako poiner, będzie 
ona wskaźnikiem. Wskaźniki 
mogą adresować różne typy 
zmiennych. Mogą adresować 
liczby, napisy, tablice, itd. Gdy 
adresują konkretny typ, nie 
deklaruje się ich jako pointer, 
tylko tworzy swój własny typ

background image

Wskaźnik do zmiennej jest po prostu jej adresem, czyli liczbą 
opisującą jej położenie w pamięci. Różnicę pomiędzy zmienną 
statyczną a wskazywaną ilustruje poniższy rysunek

Sposób obsługi zmiennych statycznych (a) i 

wskazywanych (b)

background image

Podstawianie wskaźników

• Żeby wskaźnik wskazywał adres 

jakiejś zmiennej, możemy 
napisać:

var
  wskaznik : pointer; {lub 
podobny typ}

{...}

wskaznik := 
@nazwa_zmiennej;

Znacznik @ oznacza pobranie adresu 
zmiennej. Gdybyśmy chcieli teraz coś 
zapisać pod wskazanym miejscem 
wystarczy wywołać wskazywane miejsce, 
za pomocą znaku ^

background image

wskaznik^ := nowa_wartosc;

W  podanym  przykładzie 
trzeba  by  wskaźnik  był 
zadeklarowany 

jako 

wskaźnik 

na 

liczbę. 

Gdybyśmy  bez  rzutowania 
i  bez  określenia  rodzaju 
wkaźnika  próbowali  mu 
podstawić 

wartość 

wystąpiłby  błąd  "illegal 
assigment".

background image

Każdy typ danych w Pascalu ma 
odpowiadający mu typ wskaźnikowy. Jest to 
typ, który nazywa się tak samo jak typ, 
któremu odpowiada, tylko poprzedzony jest 
znakiem '^'. Na przykład dla 
typu Integer jest to ^Integer. Zmienna 
takiego typu może przechowywać adres 
pewnych danych typu Integer w pamięci - 
dalej będziemy pisać, że zmienna "wskazuje 
na coś", ponieważ jest ona jakby strzałką 
wskazującą jakieś miejsce w pamięci. 
Zadeklarowanie zmiennej pamiętającej 
adres oraz przypisanie jej adresu innej 
zmiennej będzie wyglądać następująco:

var zmienna: Integer; 
adres_zmiennej:  ^Integer; 

begin 

zmienna := 15;

 

adres_zmiennej := @zmienna; { * }

 end.

background image

Gdy  już  jakiejś  zmiennej  wskaźnikowej  przypiszemy  adres, 
możemy tego adresu użyć, aby zobaczyć co pod tym adresem 
się  znajduje. Również  do  tego służy  operator '^',  jednak tym 
razem  stawiany  jest za nazwą  zmiennej.  Tak  więc  zmienna 
'adres_zmiennej'  służy  to  zapamiętania  gdzie  w  pamięci 
znajduje  się  pewna  zmienna,  a  za  pomocą  wyrażenia 
'adres_zmiennej^'  możemy  sprawdzić  jaka  wartość  się  tam 
znajduje (jaka jest wartość wskazywanej zmiennej). 

var zmienna: Integer;
 adres_zmiennej: ^Integer;
 begin
 

zmienna := 15; 
adres_zmiennej := @zmienna; { * }

 

writeln(zmienna);

 

writeln(adres_zmiennej^);

 end.

Dwie ostatnie linijki wypiszą dokładnie to samo 
(liczbę 15), ponieważ:
 -  zmienna ma wartość 15, gdyż taka liczba 
została jej przypisana,
 -  adres_zmiennej pokazuje na miejsce w 
pamięci, w którym znajduje się zmienna, a 
operator '^' powoduje pobranie znajdującej się 
tam wartości.

background image

Procedury

Turbo  Pascal  oferuje  kilka  metod  tworzenia  i  usuwania 
zmiennych  dynamicznych,  z  których  najpopularniejszą 
realizuje para procedur new i dispose:

new(wskaźnik-do-zmiennej)

dispose(wskaźnik-do-zmiennej)

Procedura new wykonuje czynności związane z 
utworzeniem zmiennej wskazywanej, natomiast dispose - 
operacje związane z jej usunięciem. Drugą parę 
zarządzającą dynamicznym przydziałem pamięci tworzą 
procedury GetMem i FreeMem:

GetMem(wskaźnik, rozmiar-

bloku)

FreeMem(wskaźnik, rozmiar-

bloku)

W odróżnieniu od pary new-dispose, procedury te 
wykorzystują wskaźniki amorficzne (typu pointer) i 
służą do bardziej "wewnętrznego" manipulowania 
pamięcią, tj. przydzielania i zwalniania bloków bajtów 
(a nie zmiennych wskazywanych jakiegoś konkretnego 
typu). Wielkość przydzielanego lub zwalnianego bloku 
(w bajtach) określa parametr rozmiar-bloku. 
Korzystając z obu grup procedur musisz pamiętać, że 
pamięć przydzielona przez GetMem nie może być 
zwolniona procedurą dispose i odwrotnie.

background image

mark(wskaźnik)

release(wskaźn

ik)

Wykonanie procedury mark nie powoduje 
przydzielenia pamięci ani utworzenia zmiennej, a 
jedynie zapamiętanie bieżącej "wysokości" sterty w 
zmiennej wskaźnik. Zwolnienia całego obszaru sterty 
leżącego powyżej wskaźnika dokonuje się za pomocą 
procedury release. Obydwie procedury stosowane są - 
podobnie jak GetMem i FreeMem - głównie w 
programowaniu niskiego poziomu, do "masowego" 
zwalniania pamięci przydzielonej na stercie

background image

Korzystając ze zmiennych dynamicznych 
musisz pamiętać, że sterta nie jest 
automatycznie porządkowana, toteż kolejne 
operacje przydzielenia i zwolnienia bloków 
pamięci (dowolną metodą) mogą 
doprowadzić do tzw. fragmentacji
, czyli 
rozbicia wolnego jeszcze obszaru pamięci 
na mniejsze, rozłączne bloki. Ponieważ 
rozmiar tworzonej zmiennej dynamicznej 
nie może być większy od rozmiaru 
największego wolnego bloku pamięci, może 
się okazać, że próba utworzenia zmiennej 
skończy się niepowodzeniem, mimo iż 
wielkość dostępnej pamięci będzie 
wystarczająca. Dlatego też właściwą miarą 
możliwości utworzenia większej struktury 
danych na stercie jest nie 
funkcja 

MemAvail

 (zwracająca sumaryczny 

rozmiar wolnej pamięci), 
lecz 

MaxAvail

 (zwracająca rozmiar 

największego wolnego bloku).

background image

program ZmienneDynamiczne; 
 type  TabReal = array[1..5000] of real;{ tablica 
liczb }   { rzeczywistych }  
PString = ^string; { wskaźnik do łańcucha }   
var  

s : PString; { zmienna typu wskaźnik do 
łańcucha }  
TabTabReal : array[1..100] of ^TabReal;
{ tablica }     { wskaźników }  

Sterta : pointer; { wskaźnik wysokości sterty }  
i : integer; { pomocniczy licznik }   
procedure IlePamieci;  
  begin  

writeln('Wolne: ', MemAvail, ' max. blok: ', 

MaxAvail,     ' 

bajtow.'); 

end;

background image

begin  
writeln(s^);{ zmienna nie utworzona }  
new(s);{ więc ją tworzymy }  
writeln(s^);{ utworzona, lecz nie zainicjalizowana }  
s^ := 'No wreszcie!'; { inicjalizujemy }  
writeln(s^);{ teraz jest OK }  
dispose(s);{ usuwamy }  
writeln(s^);{ zmienna nie została całkowicie zniszczona! }  
mark(Sterta);{ zaznaczamy 'poziom' sterty }  
i := 1;{ tworzymy tablicę tablic dynamicznych }  
while MemAvail > SizeOf(TabReal) do { tyle wierszy )       { ile się 
da }   
begin    

IlePamieci; { ile mamy pamięci? }    
new(TabTabReal[i]); { tworzymy nowy wiersz }    
Inc(i); { zwiększamy indeks wiersza }   
end;  

dispose(TabTabReal[3]); { usuwamy jeden wiersz tablicy }  
IlePamieci;  
release(Sterta); { zwalniamy hurtem całą pamięć }  
IlePamieci; 
end.

background image

Pierwsza część programu demonstruje etapy tworzenia, 
wykorzystania  i  usunięcia  zmiennej  wskazywanej  (w 
naszym 

przypadku 

łańcucha) 

za 

pomocą 

procedur new i dispose. 

Zauważ, 

że 

utworzenie 

zmiennej  wskazywanej  nie  jest  równoznaczne  z  jej 
inicjalizacją,  a  po  wykonaniu  procedury dispose treść 
łańcucha  nie  jest  niszczona,  chociaż  może  być 
niekompletna.
Druga  część  tworzy  typową  strukturę  wielkiej  tablicy, 
przydzielając  pamięć  dla  poszczególnych  wierszy, 
dopóki  to  jest  możliwe.  Zauważ,  że  po  usunięciu 
trzeciego wiersza tablicy na ogół okazuje się, że rozmiar 
największego  dostępnego  bloku  jest  mniejszy  od 
całkowitego  rozmiaru  wolnego  obszaru  sterty,  co 
uniemożliwia 

tworzenie 

większych 

struktur 

dynamicznych.  Wreszcie  instrukcja release zwalnia  całą 
stertę "wzwyż" począwszy od miejsca zarejestrowanego 
w zmiennej Sterta.

background image

Zapamietaj!

- Do przechowywania większych ilości danych możesz w 
Pascalu wykorzystać zmienne wskazywane (dynamiczne).
- Zmienne wskazywane są umieszczane na tzw. stercie 
(teoretycznie w dowolnym miejscu pamięci). Mogą one być 
tworzone i niszczone dynamicznie, w zależności od potrzeb.
- Zmienna wskazywana lokalizowana jest za pomocą 
wskaźnika, który zawiera jej adres (miejsce w pamięci). 
Wskaźniki mogą wskazywać na zmienne konkretnego typu, 
mogą też być wskaźnikami amorficznymi (pointer).
- Przed wykorzystaniem zmiennej dynamicznej należy ją 
utworzyć (procedurą new), a po wykorzystaniu - usunąć 
(procedurą dispose).
- Do przydzielania i zwalniania bloków pamięci na stercie 
służą również procedury GetMem, FreeMem, mark i release.

background image

Autor:

Michał Lis

3si


Document Outline