Wykład 17

Transkrypt

Wykład 17
Podstawy Programowania – semestr pierwszy
Wykład siedemnasty
1.
Wskaniki
Dotychczas wszystkie zmienne, które tworzylimy w programach, przechowywały wartoci bezporednio. Zmienna jest komórk
lub grup komórek (w zalenoci od jej typu), która przechowuje dane przetwarzane przez program. Pami operacyjn komputera
moemy wyobrazi sobie jako tablic. Elementy tej tablicy s nazywane komórkami. Kada komórka ma wielko jednego bajta.
Indeksy tej tablicy s nazywane adresami. W przypadku tablic kady element ma swój unikatowy indeks. Podobnie jest w pamici,
kadej komórce przyporzdkowany jest jednoznaczny adres (co oznacza, e nie ma dwóch komórek o tym samym adresie).
Wskaniki (zmienne wskanikowe) s specjalnymi zmiennymi, które przechowuj nie dan, ale adres komórki pamici, która t
1
dan zawiera. W jzyku Turbo Pascal najprostszym wskanikiem jest wskanik typu pointer . Zmienna tego typu ma wielko
2
4 bajtów (adres w komputerach PC jest 32 bitowy ). Typ pointer nie okrela typu danej, na któr wskazuje zmienna wskanikowa
3
4
tego typu . Ma to t zalet, e mona wskanikowi tego typu przypisa zawarto zmiennych wskanikowych innych typów , lub
odwrotnie. Wad wskanika tego typu jest niemono zinterpretowania wartoci tego, na co on wskazuje. Wskaniki typu pointer
deklarujemy w sekcji var programu lub podprogramu według nastpujcego wzorca:
nazwa_wskanika:pointer;
W praktyce czciej korzysta si ze zmiennych wskanikowych, wskazujcych na dane okrelonego typu. Typ takiej zmiennej
moemy zdefiniowa w sekcji type programu lub podprogramu, według nastpujcego wzorca:
nazwa_typu_wskanikowego=^nazwa_typu_danych;
Znak ^ oznacza, e definiujemy typ wskanikowy. Ma on równie inne znaczenie, pozwala „wydoby” dane wskazywane przez
zmienn wskanikow, co bdzie póniej zademonstrowane. Moemy równie zadeklarowa zmienn wskanikow
o „anonimowym” typie wskanikowym, według nastpujcego wzorca:
nazwa_wskanika:^nazwa_typu_danych;
Deklaracj takiego wskanika umieszczamy oczywicie w sekcji var programu lub podprogramu. Oto przykłady rzeczywistych
definicji typów wskanikowych i deklaracji zmiennych wskanikowych:
Obie zmienne (x i y) mog wskazywa na dan
type
o wielkoci jednego bajta, ale one same maj rozmiar
pbyte = ^byte;
4 bajtów, który jest konieczny do przechowania adresu
komórki pamici. Dosy czst praktyk jest
rozpoczynanie nazwy typu wskanikowego od litery „p”
var
x:pbyte;
(od angielskiego słowa „pointer”). Zmienna y została
zadeklarowana
y:^byte;
z
uyciem
anonimowego
typu
wskanikowego. Jeli zmienne wskanikowe wskazuj
na dane tego samego typu, to moliwe jest kopiowanie
ich zawartoci z jednej zmiennej do drugiej. W przeciwnym przypadku zostanie zgłoszony błd na etapie kompilacji programu.
Zmienne wskanikowe mog wskazywa oczywicie na dane innych typów ni byte. Mog to by zarówno typy wbudowane
w jzyk, takie jak string, real, shortint, longint , jak równie typy zdefiniowane przez programist, takie jak tablice i rekordy.
W przypadku wskanika na dan typu byte, moemy łatwo wytłumaczy jego działanie: zawiera on adres komórki, która zawiera
dan tego typu (przypominam, e pojedyncza komórka pamici ma wielko jednego bajta). W przypadku danych wikszych
typów, takich jak longint, real, których rozmiar jest wielokrotnoci rozmiaru komórki, zmienne wskanikowe zawieraj adres tylko
pocztkowej komórki, z grupy komórek zawierajcych dan. To, w ilu komórkach jest zapisana wskazywana dana, okrela typ tej
danej.
2.
Korzystanie ze wskaników.
Wiemy ju w jaki sposób zdefiniowa typ wskanikowy i zadeklarowa zmienn wskanikow, ale nie wiemy jeszcze w jaki sposób
1
W jzyku angielskim słowo pointer oznacza wskanik.
2
Nie do koca jest to precyzyjne stwierdzenie, jednak w analizowanych przez nas przypadkach adres zawsze bdzie 32 bitowy.
3
Wskaniki tego typu czsto okrela si wskanikami bez typu, majc na myli, e nie jest znany typ tego, na co on wskazuje.
4
„Gubic” przy tym typ tego, na co wskazywała ta zmienna.
1
Podstawy Programowania – semestr pierwszy
posługiwa si wskanikami. Poniewa zmienna wskanikowa przechowuje adres, wic moemy przypisa jej adres zmiennej,
5
która nie jest wskanikiem . Aby pozna adres zmiennej moemy uy funkcji addr, zwracajcej warto typu pointer, czyli po
prostu adres zmiennej lub uy operatora @ (czytaj: „et”), który zwraca t sam informacj. Operator ten naley do grupy
operatorów o najwyszym priorytecie. W przypadku takiego przypisania kompilator nie sprawdza zgodnoci typów, wic
wskanikiem typu byte6 mona wskazywa na zmienn typu integer. Takie rozwizanie ma oczywicie swoje zalety, ale naley go
stosowa tylko w tedy, kiedy ma si pełn wiadomo co si robi.
Wskanik, który na nic nie wskazuje zawiera warto
oznaczon stał nil, która jest „wartoci zerow” dla wskaników. Do sprawdzenia, czy wskanik ma warto rón od nil
moemy uy zwykłego operatora <> lub funkcji assigned, która jako parametr wywołania pobiera zmienn wskanikow
8
dowolnego typu, a zwraca warto typu boolean 7. W ramce poniej zamieszczono bardzo prosty program , który demonstruje
opisane wyej zastosowanie wskaników.
1
program wskazniki;
2
uses crt;
3
4
var
s:byte;
5
6
W wierszu 4 tego programu zadeklarowana jest zmienna s typu byte,
natomiast w nastpnym wierszy zadeklarowany jest wskanik w typu
byte. W wierszu 8 zmiennej s jest przypisywana warto 4, natomiast
nastpny wiersz zawiera instrukcj, która wskanikowi w przypisuje
adres zmiennej s przy uyciu operatora @. W dwóch kolejnych
w:^byte;
wierszach wypisywana jest na ekran warto zmiennej s i warto
zmiennej na któr wskazuje wskanik w. Prosz zauway, e nie
begin
7
clrscr;
8
s:=4;
9
10
w:=@s;
writeln(s);
11
writeln(w^);
12
s:=5;
13
writeln(w^);
14
w^:=3;
15
16
writeln(s);
readln;
moemy wypisa ostatniej wartoci na ekran w ten sposób: writeln(w).
Poniewa wskanik zawiera adres zmiennej, a nie jej warto, to
kompilator zasygnalizuje, e jest błd, gdy nie mona wypisa
wartoci zmiennej tego typu. Jeli chcemy wypisa zawarto
fragmentu pamici, na który wskazuje zmienna wskanikowa,
musimy wykona tzw. defeferencj wskanika, lub inaczej wyłuska
warto ze wskanika. Robimy to za pomoc znanego ju nam
operatora ^, który informuje, e chcemy pobra warto z pamici
wskazywanej przez wskanik. Po wypisaniu obu wartoci okae si,
e s one takie same, gdy wskanik w wskazuje na zmienn s. Jeli
17 end.
teraz zmienimy warto zmiennej s, to okae si e zmieniła si
równie warto, na któr wskazuje wskanik w (wiersze 12 i 13).
Moemy wykona podobn operacj posługujc si wskanikiem. W wierszu 14 zmieniamy warto tego na co wskazuje
wskanik, a nie warto samego wskanika. Po wypisaniu na ekran wartoci zmiennej s okae si, e jest ni 3, czyli e warto
9
zmiennej uległa zmianie. W zrozumieniu działania wskaników pomocny bdzie przedstawiony poniej uproszczony model
fragmentu pamici operacyjnej komputera. Załómy, e wskanikowi w odpowiada komórka o adresie 1, natomiast zmiennej s
o adresie 2. Na rysunku jest pokazany stan pamici po wykonaniu 9 wiersza programu. Zmienna s zawiera po prostu przypisan jej
warto, natomiast wskanik w zawiera adres zmiennej s.
zawarto
adres
w
1
2
s
2
4
3
4
Wskaniki, tak jak inne zmienne mog by zmiennymi globalnymi lub zmiennymi lokalnymi (w tym parametrami). Zanim zapiszemy
5
A nawet zmiennej, która jest wskanikiem, ale tego przypadku nie bdziemy analizowa, eby nie stwarza sobie dodatkowych
trudnoci w zrozumieniu zasady działania wskaników.
6
Takie sformułowanie zastpuje wyraenie: wskanik, który wskazuje na dan typu byte (jak wida jest bardziej wygodne w uyciu).
7
Osobicie nie spotkałem jeszcze programu w jzyku Pascal, który korzystałby z drugiego rozwizania, co oznacza, e jest ono
dosy rzadko stosowane.
8
Numery wierszy nie s czci kodu ródłowego programu! Zostały one dodane, aby ułatwi jego opisywanie.
9
Dokładny model fizycznej pamici komputera PC byłby zbyt szczegółowy, jak na nasze potrzeby. W modelu przyjmujemy, e
wskanik ma wielko jednego bajta, a nie czterech.
2
Podstawy Programowania – semestr pierwszy
co do fragmentu pamici, na który wskazuje wskanik, to musimy pamita aby go zainicjalizowa. Wskaniki globalne maj
domyln warto równi nil. Jeli spróbujemy przypisa warto temu, na co wskazuje ten wskanik, to wystpi błd czasu
wykonania i program zakoczy si, gdy warto nil oznacza, e wskanik na nic nie wskazuje. Bardziej niebezpieczn sytuacj
jest przypisanie wartoci temu, na co wskazuje wskanik lokalny. Podobnie jak zwykłe zmienne zawiera on warto przypadkow
i moe si okaza, e jest to adres komórki, lub grupy komórek zawierajcych rozkaz, a nie dan lub e wskanik ten wskazuje na
wane dane systemu operacyjnego (w przypadku systemu MS-DOS). Inicjalizacja wskaników jest wic szczególnie wan
czynnoci w programie.
3.
Wskaniki a podprogramy
Z pewn form wskaników spotkalimy si ju wczeniej. S to parametry formalne podprogramów poprzedzone słowem
kluczowym var. Jeli za taki parametr, w miejscu wywołania podprogramu podstawimy odpowiedni zmienn, to parametr ten
bdzie przechowywał nie warto podstawionej zmiennej, ale jej adres. Jak wiemy nie musimy stosowa operatora ^, aby otrzyma
warto wskazywan przez ten parametr, a wic jest on wygodniejszy w uyciu ni „zwykły” wskanik i dodatkowo, w przypadku
duych struktur danych pozwala zaoszczdzi pami na stosie. Tego typu wskaniki nazywane s w literaturze referencjami.
„Zwykłe” wskaniki maj szczególne zastosowanie w przypadku funkcji. Jak wiadomo funkcja nie moe zwróci wartoci typów
złoonych, takich jak tablica lub rekord. Moe jednak zwróci wskanik na warto tego typu, o ile wczeniej taki typ sobie
zdefiniujemy.
4.
Zmienne dynamiczne
Wskaniki maj jeszcze inne, bardziej zaawansowane zastosowanie. Pozwalaj one tworzy, tzw. zmienne dynamiczne.
Przyjrzyjmy si rodzajom zmiennych, jakie s stosowane w programach napisanych w jzyku Pascal. Do tej pory poznalimy dwa
takie rodzaje. Pierwszy, to zmienne globalne, które umiejscowione s w segmencie (obszarze pamici) danych. S one tworzone
podczas uruchamiania programu i usuwane po jego zakoczeniu, innymi słowy istniej cały czas, podczas działania programu.
Drugim rodzajem zmiennych s zmienne lokalne (w tym parametry) podprogramów. Takie zmienne tworzone s na stosie podczas
10
wywołania podprogramu i niszczone po zakoczeniu jego wykonania (std inn nazw tego rodzaju zmiennych jest „zmienne
automatyczne”). Zmienne lokalne nie istniej wic cały czas w pamici komputera, ale s tworzone automatycznie, wtedy kiedy s
potrzebne i usuwane, kiedy staj si zbdne. Trzecim rodzajem zmiennych s zmienne dynamiczne. O tym kiedy bd one
utworzone, a kiedy usunite z pamici komputera decyduje programista. Moe on równie mie wpływ na ilo pamici
zajmowanej przez tak zmienn. Zmienne dynamiczne pozwalaj wic na lepsz gospodark pamici komputera. Pozwalaj
równie na tworzenie skomplikowanych struktur danych (tym zagadnieniem bdziemy si zajmowa na przyszłych wykładach).
Zmienne dynamiczne tworzone s w obszarze pamici komputera, który nazywa si stert (ang. heap). Program napisany w jzyku
Pascal, zaraz po uruchomieniu ma przydzielan prawie cał pami operacyjn dostpn z poziomu systemu operacyjnego DOS
(640KB). Na segment danych przeznaczy jest obszar o wielkoci 64KB, a wic sumaryczna wielko zmiennych globalnych nie
moe przekroczy tej wielkoci. O tym ile pamici jest przeznaczonej na stos i stert moemy si dowiedzie wywietlajc
w rodowisku TP wszystkie dyrektywy kompilatora (wystarczy nacisn <Ctrl> + <o> + <o>) i sprawdzi wartoci, jakie stoj za
11
dyrektyw $M. Typowo bdzie to: {$M 16384,0,655360}. Pierwsza warto okrela, ile bajtów jest przeznaczonych na stos . Dwie
nastpne okrelaj minimaln i maksymaln wielko sterty, równie w bajtach. Moemy do pewnego stopnia modyfikowa te
wartoci, w szczególnoci, jeli nie bdziemy si w programie posługiwa zmiennymi dynamicznymi moemy obszar sterty
zmniejszy do zera. Zmienn dynamiczn tworzymy poprzez wywołanie odpowiedniego podprogramu, który przydziela (alokuje)
pami na t zmienn i zapisuje adres do niej we wskaniku. Zmienna wskanikowa jest wic rodzajem łcznika midzy zmienn
dynamiczn, a reszt programu. Program bezporednio nigdy nie operuje na zmiennej dynamicznej, zawsze wszelkie operacje na
niej wykonuje za porednictwem wskanika. Naley uwaa, aby nie „zgubi” podczas działania programu adresu zmiennej
dynamicznej. Jeli si tak stanie, nie bdziemy w stanie zwolni pamici na niej przydzielonej i po pewnym czasie moe si
okaza, e nie bdziemy w stanie przydzieli pamici na nowe zmienne, bo cała sterta jest ju zajta. „Gubienie" pamici
przydzielonej na zmienne dynamiczne nazywane jest w gwarze informatycznej „wyciekaniem pamici” lub „wyciekami pamici”
(ang. memory leaks). Pami przydzielon na zmienn dynamiczn powinnimy zwolni (zdealokowa) w momencie, kiedy ta
zmienna przestanie by nam potrzebna, w szczególnoci powinnimy zwolni pami przeznaczon na zmienne dynamiczne
przed zakoczeniem programu. Naley pamita o tym, e nie wolno si odwoływa poprzez wskanik do zmiennej dynamicznej,
której obszar pamici został zwolniony.
10 Dokładniejszy opis mechanizmu tworzenia jest opisany w jednym z wykładów z pierwszego semestru.
11 Jak wida, jest to niewielka ilo (około 16KB) i dlatego naley t pami oszczdza.
3
Podstawy Programowania – semestr pierwszy
5.
Przydzielanie i zwalnianie pamici
Do alokacji i dealokacji pamici w jzyku Turbo Pascal słuy kilka par procedur. My zapoznamy si z trzema najpopularniejszymi.
S to new (przydział) i dispose (zwolnienie), getmem (przydział), freemem (zwolnienie), mark (zaznaczenie) i release (usunicie).
Procedura new pobiera jeden argument wywołania i jest nim wskanik do zmiennej dynamicznej. Procedura ta przydziela na
stercie, tyle pamici, ile wynika z typu danej, któr moe wskazywa zmienna wskanikowa i zapisuje adres pocztku tej pamici
do przekazanego jej wskanika. Procedura dispose jest wywoływana z tym samym argumentem co new i zwalnia tyle pamici na
12
stercie ile wynika z typu argumentu. Po zwolnieniu pamici warto wskanika zazwyczaj nie ulega zmianie , jednak do obszaru
pamici na który on wskazuje nie wolno ju si odwoływa. Procedura getmem pobiera dwa argumenty: wskanik i rozmiar
pamici, wyraony w bajtach któr naley przydzieli na stercie. Rozmiar ten moe by przekazany bezporednio jako stała lub
wprost – warto, moe te by okrelony wyraeniem, lub zapisany w zmiennej. Procedura freemem jest wywoływana z takimi
samymi argumentami (w szczególnoci rozmiar zwalnianej pamici powinien by ten sam, aby unikn wycieków). Jeli rozmiar
okrelimy za pomoc funkcji sizeof, podajc jako jej argument wskanik przekazywany do getmem lub freemem, to uzyskamy takie
samo działanie, jak w przypadku new i dispose. Działanie dwóch ostatnich procedur jest troch zawiłe. Kompilator niejawnie (bez
naszej wiedzy) dodaje do kadego programu fragment kodu, który zarzdza stert (tzw. menader sterty). Czci tego
menadera s przedstawione procedury, oraz takie zmienne, jak HeapOrg, która zawiera adres pocztku sterty, HeapPtr, która
zawiera adres pocztku wolnego miejsca na stercie, HeapEnd , która zawiera adres koca sterty. Procedura mark przepisuje adres
przechowywany we wskaniku HeapPtr, do wskanika, który został jej przekazany jako parametr wywołania. Procedura release
zwalnia pami poczwszy od adresu na który wskazuje zmienna HeapPtr, do adresu, który został jej przekazany we wskaniku
bdcym argumentem jej wywołania. W ramce poniej znajduje si kod ródłowy bardzo prostego programu demonstrujcego
uycie procedur mark i release, a obok ilustracja objaniajca jego działanie.
1 program mark_and_release;
Po wykonaniu wiersza 9
2
Po wykonaniu wiersza 13
Sterta
3 var
4
p1,p2,p3:pointer;
5
6 begin
7
getmem(p1,400);
8
mark(p2);
p2 = HeapPtr
p2
9 getmem(p3,200);
10 getmem(p3,100);
11 getmem(p3,500);
12 release(p2);
13 freemem(p1,400);
14 end.
HeapPtr
Jak wynika z rysunku, po wykonaniu 9 wiersza programu, we wskaniku p2 została zapamitana bieca warto wskanika
HeapPtr. W kolejnych wierszach były przydzielane obszary pamici na stercie, kolejno o wielkociach 200, 100 i 500 bajtów, przy
czym adresy pocztków tych obszarów były celowo „gubione” (wartoci wskanika p3 jest zmieniana podczas kadego wywołania
procedury getmem). Obszary te mona było zwolni wyłcznie dziki procedurze release , która wykonała t operacj w sposób
pokazany na rysunku. Poniewa pami, na któr wskazywał wskanik p1 była przydzielona przed wywołaniem procedury mark, to
nie zostaje ona zwolniona przez release i naley j zwolni przez freemem. W programie uyto wskaników typu pointer, poniewa
nie odwoływalimy si do zawartoci obszarów pamici przydzielonych przez getmem. Pomimo, e uycie procedury mark i release
12 Precyzyjniej – w przypadku Turbo Pascala i innych kompilatorów Pascala firmy Borland nie ulega ona zmianie, natomiast jeli
program został skompilowany za pomoc kompilatora Free Pascala, to wskanik jest ustawiany na nil.
4
Podstawy Programowania – semestr pierwszy
wydaje si by proste i wygodne, to moe prowadzi do błdów, jeli podczas pracy programu wielokrotnie przydzielamy
i zwalniamy pami. Zazwyczaj wystrzegamy si stosowania tych procedur i uywamy ich tylko w ostatecznoci.
6.
Dynamiczne tablice
Zmienne dynamiczne nie tylko mog by zmiennymi prostych typów, jak integer lub real , lecz mog równie by skomplikowanymi
strukturami danych. Takie struktury w jzyku Pascal buduje si w oparciu o typ rekordowy i bd one przedmiotem nastpnych
wykładów. Na tym wykładzie zajmiemy si tablicami. Przyjrzyjmy si na wstpie dwóm definicjom typów:
type
ptablica = ^tablica;
tablica = array [1..10] of integer;
tablica1 = array [1..10] of ^integer;
Dwa pierwsze wiersze po słowie type naley rozwaa razem. Pierwszy zawiera definicj typu wskazujcego na typ tablica. Prosz
zauway, e ten drugi typ nie został jeszcze zdefiniowany. Jest to jedyny przypadek, kiedy kompilator Pascala pozwoli nam na
co takiego. W tym przypadku moliwa jest zamiana kolejnoci tych wierszy, ale w przypadku innych struktur danych moe si to
okaza niemoliwe i taki mechanizm definiowania typów zmiennych bdzie podany. Drugi wiersz zawiera definicj „zwykłego”
typu tablicowego, a wic wskaniki typu ptablica bd wskazywa na tablice o 10 elementach typu integer. Wiersz trzeci zawiera
równie definicj tablicy, ale jest to tablica wskaników, do zmiennych typu integer. Zmiennych tego typu bdziemy uywa w róny
sposób. Jeli zechcemy przydzieli pami na zmienn dynamiczn, wskazywan przez wskanik pierwszego typu, to wystarczy
pojedyncze wywołanie getmem lub new . Zwolnienie tej pamici równie wykonamy za pomoc pojedynczego wywołania dispose lub
freemem. W przypadku drugiego typu, musimy w ptli przydzieli pami, na kady z elementów tej tablicy i równie w ptli zwolni
pami przydzielon na kady z tych elementów. Inaczej równie odwołujemy si do tych wskaników. W pierwszym przypadku
mamy do czynienia ze wskanikiem na tablic, a wic odwołujemy si do obszaru pamici wskazywanego przez ten wskanik
według wzorca:
nazwa_wskanika^[indeks]
czyli, np.: tab^[i]. W drugim przypadku mamy do czynienia z tablic wskaników, a wic do wartoci jej elementów bdziemy
odwoływa si w nastpujcy sposób:
nazwa_wskanika[indeks]^
czyli, np.: tab[i]^. W ramce poniej znajduje si kod ródłowy programu, który korzysta z dynamicznej tablicy elementów typu
integer.
W wierszach 4 i 5 tego programu jest zdefiniowany typ
1 program memory;
wskanikowy na tablic elementów typu integer. W wierszu 8 jest
utworzony wskanik tego typu. Ten wskanik bdzie parametrem
wywołania procedur zapeln i pokaz (wiersze 34 i 35). Do obu tych
2 uses crt;
3 type
4
wskaznik=^tablica;
5
tablica = array [1..10] of integer;
procedur wskanik ten zostanie przekazany przez zmienn, cho
tylko w przypadku procedury zapeln jest modyfikowana jego
zawarto. W wierszu 15 przydzielana jest pami na tablic
dynamiczn i jej adres jest zapisywany do wskanika (zamiast
getmem mona uy new , ale poniewa w wikszoci programów
zaprezentowanych na przyszłych wykładach bdziemy posługiwa
6
7 var
8
si t drug procedur, to warto zobaczy, jak uywa si tej
pierwszej). Prosz zwróci uwag na wykorzystanie funkcji sizeof
tab:wskaznik;
do okrelenia iloci pamici potrzebnej na t tablic (freemem
9
dodaje 4 bajty do tego rozmiaru, które s potrzebne menederowi
sterty). Po przydzieleniu pamici na t tablic, w ptli jej
elementom nadawana jest warto ich indeksów (najprostszy
10 procedure zapeln(var t:wskaznik);
11 var
sposób
12 i:byte;
automatycznego
wypełnienia
tablicy).
Do
tablicy
odwołujemy si w opisany wyej sposób. W wierszu 14 tej
procedury wypisujemy na ekran ilo dostpnej na stercie wolnej
13 begin
pamici. T ilo zwraca nam procedura memavail. Jest to warto
typu longint. Wypisanie wartoci zwracanej przez t funkcj jest
5
Podstawy Programowania – semestr pierwszy
najprostszym
sposobem
sprawdzenia
poprawnoci
przydziału
14
writeln('Dostpna pami: ',MemAvail);
15
getmem(t,sizeof(tablica));
elementów tablicy dynamicznej na ekran (wiersze 25 i 26) oraz
16
for i:=low(tablica) to high(tablica) do
zwalnia pami przydzielon na t tablic (wiersz 27). Przed (wiersz
24) i po zwolnieniu tej pamici (wiersz 29) wypisywana jest ilo
17
t^[i]:=i;
18 end;
i zwolnienia pamici na stercie. Procedura pokaz wypisuje wartoci
wolnej pamici na stercie. Obie te wartoci powinny by równe. Inn
prost metod kontroli poprawnoci przydziału i zwalniania pamici
jest zapamitanie iloci dostpnej pamici przed wykonaniem
19
jakiegokolwiek przydziału i odjcie od niej iloci pamici po
20 procedure pokaz(var t:wskaznik);
wszystkich zwolnieniach. Jeli zarzdzanie pamici w programie
było poprawne, to powinnimy otrzyma w wyniku zero. Ten sposób
21 var
bdzie zaprezentowany na przyszłych wykładach.
22 i:byte;
23 begin
24
writeln('Dostpna pami: ',MemAvail);
25
for i:=low(tablica) to high(tablica) do
26
write(t^[i]:3);
27
FreeMem(t,sizeof(tablica));
28
writeln;
29
writeln('Dostpna pami: ',MemAvail);
30 end;
31
32 begin
33 clrscr;
34 zapeln(tab);
35 pokaz(tab);
36 readln;
37 end.
W zaprezentowanym programie przydzielalimy na stercie pami na tablic, któr moglibymy równie dobrze umieci
w segmencie danych (zdeklarowa jako zmienn globaln). Okazuje si jednak, e korzystajc ze zmiennych dynamicznych
mona utworzy wiksz tablic, ni te, które moemy zmieci w segmencie danych (powyej 64KB). Załómy, e chcemy
utworzy tablic, o 500 tysicach elementów typu byte. Problem polega na tym, e kompilator nie dopuci do tego, abymy
utworzyli typ tablicowy o takiej iloci elementów. Musimy
wic posłuy si pewn sztuczk: moemy np.: utworzy
1 {$A+,B-,D+,E+,F-,G-,I+,L+,N-,O-,P-,Q-,R-,S+,T-,V+,X+,Y+}
tablic 50 wskaników na .... tablice o 10 tysicach
2 {$M 16384,0,655360}
elementów typu byte. Obok znajduje si program, który
tworzy, wypełnia, wywietla i niszczy tak tablic.
3 program duzatablica;
4 uses crt;
Wiersze
1
i
2
programu
zawieraj dyrektywy
5 type
kompilatora, w szczególnoci wiersz drugi zawiera
6
tablica = array [0..9999] of byte;
dyrektyw odpowiedzialn za przydział pamici na
stos i stert. W wierszu 6 jest definiowany typ
7
tablicaw = array [0..49] of ^tablica;
tablicowy okrelajcy tablic o 10000 elementów typu
byte, a wierszu 7 definiowany jest typ tablicowy
8 var
6
Podstawy Programowania – semestr pierwszy
okrelajcy
9 tab:tablicaw;
tablic
wskanikami
na
o
50
tablice
elementach
typu
bdcych
zdefiniowanego
10 i:longint;
w poprzednim wierszu. Wiersz 9 zawiera deklaracj
11
wskanika globalnego typu zdefiniowanego w wierszu
7. W wierszu 10 znajduje si z kolei deklaracja
12 procedure alokuj(var t:tablicaw);
zmiennej typu longint, która bdzie słuyła jako indeks
elementów
13 var
tej
tablicy.
Procedura
bdzie
alokuj
pobiera tylko jeden parametr wywołania (wiersz 51)
14
i:byte;
i jest to tablica wskaników na tablice. W wierszu 16
na kady z elementów tej tablicy jest przydzielana
pami na stercie. Komplementarn do procedury
15 begin
16
for i:=0 to 49 do new(t[i]);
alokuj jest procedura dealokuj, która zwalnia pami
przydzielon na elementy tej tablicy. Funkcja pobierz
17 end;
zwraca warto okrelonego elementu tablicy. Aby
18
pozostawi uytkownikowi złudzenie, e ma do
19 function pobierz(var t:tablicaw;const i:longint):byte;
czynienia z jedn, du tablic warto indeksu
elementu jest dzielona na dwie czci: warto
20 var
indeksu (x) tablicy wskaników i warto indeksu
21 x:byte;
elementu wskazywanej tablicy (y). Te czci s
wyznaczane za pomoc operatorów div i mod, dlatego
22 y:word;
zakresy wartoci indeksów obu tablic rozpoczynaj si
23 begin
od zera. Za pomoc dzielenia całkowitego ustalamy
indeks elementu tablicy wskaników, wskazujcego na
24 x:=i div 10000;
odpowiedni tablic elementów typu byte (wiersz 24),
25 y:=i mod 10000;
a za pomoc reszty z dzielenia otrzymujemy indeks
elementu tej ostatniej tablicy (wiersz 25). Prosz
26 pobierz:=t[x]^[y];
zwróci uwag na sposób odwoływania si do tablicy.
27 end;
Dereferencja wskanika nastpuje po indeksie x, gdy
to on zwizany jest z tablic wskaników, drugi indeks
28
(y) dotyczy ju „zwykłej” tablicy, chocia jest ona
29 procedure wstaw(var t:tablicaw; i:longint; a:byte);
umieszczona na stercie. Procedura wstaw odwołuje si
do tablicy w ten sam sposób, ale nie odczytuje
30 var
wartoci elementu, tylko j do niego zapisuje.
31 x:byte;
Wypisanie zawartoci tablicy nastpuje w wierszach
53 – 64, w bloku głównym programu. Wpisywanie jest
32 y:word;
wstrzymywane
33 begin
uytkownika
do
czasu
dowolnego
nacinicia
klawisza,
jeli
przez
warto
zmiennej indeksujcej jest podzielna przez 500.
34 x:=i div 10000;
W programie przy pomocy dwóch tablic, w tym jednej
bdcej tablic wskaników, oraz kilku podprogramów
35 y:=i mod 10000;
36 t[x]^[y]:=a;
została stworzona iluzja, e mamy do czynienia ze
37 end;
zwykł tablic o duej iloci elementów. T tablic
38
termin oraz inne zagadnienia zwizane z nim zostan
39 procedure dealokuj(var t:tablicaw);
wyjanione na nastpnych wykładach.
moemy uzna za abstrakcyjn struktur danych. Ten
40 var
41 i:byte;
42 begin
43 for i:=0 to 49 do
44
dispose(t[i]);
45 end;
7
Podstawy Programowania – semestr pierwszy
46
47 begin
48 clrscr;
49 randomize;
50 writeln(memavail);
51 alokuj(tab);
52 for i:=1 to 500000 do wstaw(tab,i,random(100));
53 for i:=1 to 500000 do
54 begin
55
write(pobierz(tab,i):5);
56
if i mod 500 = 0 then
57
begin
58
while keypressed do readkey;
59
repeat
60
until keypressed;
61
if readkey=#27 then break;
62
clrscr;
63
end;
64 end;
65 writeln;
66 writeln(memavail);
67 dealokuj(tab);
68 writeln(memavail);
69 readln;
70 end.
7.
Podsumowanie
Ten wykład nie wyczerpuje listy wszystkich zagadnie zwizanych z dynamicznym zarzdzaniem pamici i wskanikami. Jego
celem jest tylko wprowadzenie do tej tematyki. Zainteresowane osoby mog znale opis innych zagadnie z tej dziedziny
w ksikach powiconych programowaniu i systemom operacyjnym.
8

Podobne dokumenty