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