12. A Turbo Pascal memóriahasználata 12.1. A mutató típus - dinamikus változók Az eddig használt egyszerű és struktúrált típussal rendelkező változók közös tulajdonsága, hogy rájuk a deklarálás során megadott névvel hivatkozhatunk. Az így definiált változók mérete a program fordításakor dől el, és a program futása során nem módosítható. A Pascal nyelvben ezeket a változókat statikus változóknak nevezzük. A hatékony memóriafelhasználás érdekében szükség van arra, hogy bizonyos memóriaterülettel a programozó saját maga gazdálkodhasson - ha szükség van terület kijelölésére valamely változónk számára, azt onnan elvégezhessük, illetve ha már nincs szükségünk a változóra, a lefoglalt területet felszabadíthassuk. Ilyen módon megvalósíthatjuk a memória dinamikus felhasználását. A dinamikus változók számára rendelkezésre álló területet a Turbo Pascal-ban halomterületnek (heap) nevezzük. Ezen terület mérete jóval meghaladja program egyéb adatterületeinek méretét. A kérdés most már csak az, hogy hogyan férhetünk hozzá a heap területhez? Mint ahogy azt az előző fejezetekben láttuk, a var A:integer; deklaráció hatására a fordítóprogram lefoglal a memóriában egy 2 byte hosszúságú területet az A változó számára. Az A változóra a nevének felhasználásával hivatkozhatunk: A:=22; A:=A+10; Ahhoz, hogy a memóriafoglalás fenti lépéseit a program futása során végezhessük el, meg kell ismerkednünk egy új változótípus, a mutató (pointer) fogalmával. A mutató típusú változó olyan speciális változó (4 byte hosszú), amely memóriacímet tartalmaz. A magyar nyelvű szakirodalom a mutató és a pointer szavakat egyaránt használja. A mutató deklarálása: var ap : ^integer; A ^ jel után tetszőleges szabványos vagy felhasználó által definiált típus állhat. Az ap mutató a deklarálás után semmilyen meghatározott címet sem tartalmaz, tehát "sehova sem mutat". Turbo Pascal-ban a mutatónak többféleképpen lehet értéket adni. Most azonban tekintsük csak a legegyszerűbb szabványos megoldást. A programon belüli helyfoglalás elvégzésére a new eljárás használható, amely a lefoglalt terület címét a paraméterként átadott mutatóba tölti: new(ap); Ha nincs elegendő hely a halomterületen, a programunk 203-as futás közbeni hibajelzéssel leáll: "Heap overflow error". A hibajelzés elkerülhető, ha a new aktiválása előtt ellenőrizzük, hogy van-e elegendő szabad terület a heap-en. Hasonlítsuk össze a maxavail függvény által visszaadott maximális szabad blokk méretét az integer változó tárolásához szükséges mérettel (sizeof(integer)). Ekkor az előző programsor a következőkkel helyettesíthető: if maxavail=sizeof(tombt) do begin inc(maxi); new(ptrtomb[maxi]); writeln(maxi,'. tömb foglalása kész'); end; { a tömbök feltöltése 0.0 értékkel} for i:=1 to maxi do begin for j:=1 to 10000 do ptrtomb[i]^[j]:=0.0; writeln(i,'. tömb feltöltése kész'); end; {a tömbök által lefoglalt terület felszabadítása} for i:=1 to maxi do begin dispose(ptrtomb[i]); writeln(i,'. tömb felszabadítása kész'); end; end. 12.1.3. A lista tárolási szerkezet A dinamikus adatterületek optimálisan a lista struktúra segítségével kezelhetők. A lista struktúra Pascal nyelven történő megvalósításához a rekord típus használható. Az így keletkező lista tulajdonképpen egy rekordláncnak tekinthető. A legegyszerűbb struktúrájú lista (lineáris lista) alapján megnézzük a listák kezelésének alapvető lépéseit. A lista elemei a listaelem tartalmaz adatokat és mutatót a következő listaelemre: adat mutató A lista felépítése pedig a következő a lista vége {SYMBOL 175 \f "Symbol"} adat mutató {SYMBOL 190 \f "Symbol"}{SYMBOL 174 \f "Symbol"} adat mutató {SYMBOL 190 \f "Symbol"}{SYMBOL 174 \f "Symbol"} adat nil {SYMBOL 173 \f "Symbol"} a lista kezdete Az alábbi példában tetszőleges hosszúságú (max 64K/5) sztring kezelését valósítjuk meg lineáris lista felhasználásával. A listelemet a karakter típusú rekordban tároljuk. A var strptr:mutato; deklaráció felhasználásával az strptr^ jelöli a karakter típusú rekordot (new(strptr); után). Mint ismeretes a rekord mezőire hivatkozhatunk a . (pont) jellel: strptr^.ch :='A'; strptr^.kovetkezo:=nil; vagy pedig a with utasítás segítségével: with strptr^ do begin ch :='A'; kovetkezo:=nil; end; E rövid bevezető után tekintsük meg magát a programot: program pt3; uses crt; type mutato=^karakter; karakter=record ch:char; kovetkezo:mutato; end; var str_ptr, {pointer a krakterlánc kezdetére } char_ptr:mutato; {pointer a lánc valamely eleméhez } mem0:longint; procedure char_read(var list_ptr:mutato); { feladat:egy karakter beolvasása a billentyűzetről és hozzáfűzése a lánchoz } {parameter: a hozzáfűzött karakter mutatója } var k:char; begin k:=readkey; if ord(k) in [32..255] then begin write(k); new(list_ptr); list_ptr^.ch:=k; list_ptr^.kovetkezo:=nil; end else list_ptr:=nil; end; procedure str_read(var list_ptr:mutato); {feladat : karaktersorozat beolvasása a billentyűzetről } {parameter : a karaktersorozat mutatója } var tmp_ptr:mutato; begin char_read(list_ptr); { az első karakter bevitele } tmp_ptr:=list_ptr; while tmp_ptr<>nil do begin char_read(tmp_ptr^.kovetkezo); tmp_ptr:=tmp_ptr^.kovetkezo; end; end; procedure char_print(list_ptr:mutato); {feladat: karakterlánc megjelenítese a képernyőn } {parameter: mutató a megjelenítendő karakterlánc kezdetére } begin writeln; while list_ptr<>nil do begin write(list_ptr^.ch); list_ptr:=list_ptr^.kovetkezo; end; writeln; end; function str_len(list_ptr:mutato):integer; {feladat: karakterlánc hosszának lekérdezése } {parameter: mutató a karakterlánc kezdetére } var h:integer; begin h:=0; while list_ptr<>nil do begin inc(h); list_ptr:=list_ptr^.kovetkezo; end; str_len:=h; end; procedure str_free(list_ptr:mutato); {feladat: karakterlánc megszüntetése } {parameter:mutató a felszabadítandó karakterlánc kezdetére } var str_ptr:mutato; begin while list_ptr<>nil do begin str_ptr:=list_ptr^.kovetkezo; dispose(list_ptr); list_ptr:=str_ptr; end; end; begin clrscr; mem0:=memavail; { a kiindulási heap-méret } write('Kérek egy karaktersorozatot: '); str_read(str_ptr); {a sztring beolvasása és tárolása } char_print(str_ptr);{ a karakterlánc kiírása } writeln('A sztring hossza : ', str_len(str_ptr),' byte'); writeln('A lefoglalt terület : ',mem0-memavail, ' byte'); str_free(str_ptr); { a sztring törlése a memóriából } end. 12.1.4. Saját verem kialakítása Az alábbi példában 2..32 alapú számrendszerbe alakítunk át decimális számokat. Az algoritmus a maradékos osztás elvén alapul. Tegyük fel, szeretnénk az 1206 számot átalakítani 16-os számrendszerbe, a maradékos osztás felhasználásával. Az algoritmus menete az alábbi ábrán látható: 1206 16 75 6 4 11 0 4 A maradékokat fordított sorrendben összeolvasva kapjuk az új számot: 4B6. A megoldásban a verem szerkezetet a keletkező maradékok dinamikus tárolására és fordított sorrendben történő kiolvasására használjuk. program pt4; { A feladat megoldásához a saját verem (stack) használata ajánlott} type mutato = ^elem; elem = record szamjegy: byte; elozo : mutato; end; var alap,i: integer; szam : longint; verem, seged:mutato; var jegyek:array[0..31] of char; begin { a lehetséges számjegyeket tartalmazó tömb feltöltése} for i:=0 to 9 do jegyek[i]:=chr(48+i); for i:=10 to 31 do jegyek[i]:=chr(55+i); write('Kérem a számrendszer alapját:',#9#9); readln(alap); if not (alap in [2..32]) then halt; write('Az átváltandó szám:', #9#9#9); readln(szam); { A maradékok elhelyezése a veremben, } { a helyfoglalás elvégzésével } verem:=nil; while szam<>0 do begin new(seged); seged^.szamjegy:= szam mod alap; seged^.elozo := verem; verem := seged; szam := szam div alap; end; { A szám kiírása a veremből történő, } { visszaolvasással } write('Az adott számrendszerbeli alak:',#9#9); seged := verem; while seged<>nil do begin write(jegyek[seged^.szamjegy]:1); seged := seged^.elozo; end; writeln(#13#13); { A verem által lefoglalt memória- } { terület felszabadítása } seged:= verem; while seged<>nil do begin verem := seged^.elozo; dispose(seged); seged:= verem; end; end. 12.2. További lehetőségek a memória elérésére Az 12.1. részben ismertetett szabványos megoldásokon felül a Turbo Pascal egy sor új lehetőséget tartalmaz a számítógép memóriájának elérésére. Ahhoz azonban, hogy élni tudjunk ezekkel a lehetőségekkel mélyebben meg kell ismerkednünk a memória felépítésével és címzésével. 12.2.1. Amit a 8086 mikroprocesszorról tudni kell Ahhoz, hogy teljes mértékben kezünkben tarthassuk a Pascal programok memóriahasználatát, nézzük meg, hogyan is címzi a memóriát az IBM PC mikroprocesszora. A 8086 mikroprocesszor 16 bites (16 bitnyi információt fogad, illetve küld ki egy lépésben) - ennek megfelelően minden belső regisztere 16 bites. A 8086/88 mikroprocesszor maximálisan 1 Mbyte memória címzésére képes, amelyhez 20 bites fizikai címet használ. A kérdés ezek után csak az, hogyan lehet 16 bites regiszterek segítségével előállítani ezt a 20 bitet. A megoldást a memória szegmentált címzése adja, amely a 12.1. ábrán látható. Ha megnézzük a fizikai címeket, akkor azt tapasztaljuk, hogy minden 16-dik cím olyan, hogy az alsó 4 bitje 0 ($ssss0), így ezen címek azonosításához elegendő a felső 16 bitet használni ($ssss). Ezzel a 16 bites értékkel (szegmenscím) egy ún. szegmenst jelölünk ki a memóriában. Ezek után a kívánt byte címe egyszerűen megadható a szegmens kezdetétől mért távolsággal, amely szintén 16 bites érték ($xxxx - offszetcím). Mivel az offszet 16 bites, egy szegmens maximális mérete 64 Kbyte, amely korlátozás lépten nyomon kísért a Pascal programunk fejlesztése során. A szegmes- és offszetcím segítségével a memória tetszőleges területe megcímezhető a $ssss : $xxxx (szegmenscím : offszetcím) alakban. Például a színes szöveges képernyő pufferterülete a $b800:0 címen kezdődik. {EMBED MSDraw \* mergeformat|} 12.1. ábra A memória szegmentált címzése A Turbo Pascal a címeket 4 byte hosszú területen tárolja: {EMBED MSDraw \* mergeformat|} A 8088/86 mikroprocesszor egyszerre maximálisan négy szegmenst képes megcímezni az ún. szegmensregiszterek felhasználásával: CS - a kód (futó program) szegmense DS - az adatszegmens SS - a stack szegmense ES - másodlagos adatszegmens (általában munkaterület) Mivel ezen regiszterek tartalma megváltoztatható, így 64 Kbyte-nál nagyobb kódú program is előállítható. Turbo Pascal programból, az MS- DOS operációs rendszer alatt a felhasználható RAM (írható/olvasható memória) terület maximális mérete 640 Kbyte. 12.2.2. A Turbo Pascal és a szegmentált memória A Turbo Pascal program fejlesztése során nem szabad megfeledkeznünk a 64 Kbyte-os szegmensméretről. Mielőtt bizonyos következtetéseket levonnánk, tekintsük meg a 12.2. ábrán a futó Turbo Pascal program elhelyezkedését a memóriában. Mire kell figyelnünk a program írása során? - Mint ahogy az ábrán látható a Turbo Pascal kódrésze több (legalább két), szegmensből áll. Minden egyes szegmens maximális mérete 64 Kbyte lehet. Ha tehát 64 Kbyte-nál nagyobb kódú programot kell írnunk, ezt csak akkor tehetjük meg, ha a programot részekre (modulokra - unit- okra) bontjuk. {EMBED MSDraw \* mergeformat|} 12.2. ábra A futó Turbo Pascal program elhelyezkedése a memóriában - Nagyon súlyos korlát az adatszegmens 64 Kbyte-os határa. A program által használt statikus adatok (globális változók és típusos konstansok) által használt memória nem haladhatja meg a 64 Kbyte-ot. Ezt a kötöttséget csak a dinamikus változók bevezetésével lehet feloldani. - A verem (stack) maximális mérete szintén 64 Kbyte, amely érték az eljárásokban és függvényekben használt lokális változók mennyiségét korlátozza. Ne feledjük el azonban, hogy lokális mutató segítségével szintén használhatunk heap-területet, amelyet azonban az alprogramból való kilépés előtt fel kell szabadítanunk… - Az ábráról nem olvasható le, de tudnunk kell, hogy a Turbo Pascal- ban tetszőleges adatstruktúra (tömb, rekord) maximális mérete szintén 64 Kbyte. A $M globális fordítási dírektíva megadásával beállítható a program stack és heap területe. A $M alapértelmezés szerinti formájában {$M 16384,0,655360} a program 16 Kbyte verem- illetve maximális (a 640 Kbyte-os határig) halomterülettel rendelkezik. A direktíva általános alakja: {$M stack-méret, minimális heap-méret, maximális heap-méret} (heapmin) (heapmax) A paraméterek lehetséges étékei: paraméter Minimum Maximum stack-méret 1024 65520 heapmin 0 655360 heapmax heapmin 655360 A heapmin azt a minimális halomterület méretet definiálja, amely megléte esetén indul csak el a programunk. A heapmax a futás során felhasználható maximális heap-területet méretet adja meg. 12.2.3. Speciális lehetőségek a memória elérésére 12.2.3.1 A Mark és a Release eljárások használata A mark eljárás segítségével valamely mutatóban feljegyezhetjük a heap állapotát. Ez a feljegyzett állapot a release eljárás hívásával egyszerűen visszaállítható. Másképp fogalmazva, a release hívással az összes mark után lefoglalt memóriablokk felszabadításra kerül. program mark_release; type arr=array[1..20000] of byte; var ap,bp,cp,mp : ^arr; begin writeln; writeln('A New előtt ',memavail); New(ap); writeln('A Mark előtt ',memavail); Mark(mp); New(bp); New(cp); Writeln('A Release előtt ',memavail); Release(mp); Writeln('A Release után ',memavail); Dispose(ap); Writeln('A Dispose után ',memavail); end. A program az alábbi eredményt adja, ahonnan jól leolvasható a heap kezelése a mark és release eljárások használatával: A New előtt 599232 A Mark előtt 579232 A Release előtt 539232 A Release után 579232 A Dispose után 599232 12.2.3.2. A Getmem és a Freemem eljárások használata A Turbo Pascal lehetőséget biztosít pointer típusú általános mutató deklarálására. Az így létrehozott mutató nem rendelkezik konkrét típussal, ezért a New eljárást nem használhatjuk a memóriafoglalás elvégzésére. Ebben az esetben a dinamikus memóriahasználatot a Getmem és a Freemem eljárások hívásával végezhetjük el: Getmem (var p:pointer; size:word); illetve Freemem (var p:pointer; size:word); A megadható maximális blokkméret 65521 (64K - $F) byte. Ha a foglalásnál nem áll rendelkezésre elegendő szabad heap-terület, akkor a program 203-as hibával leáll. A Getmem és Freemem eljárások használhatók típussal rendelkező mutatók esetén is, ahol a szükséges méretet a sizeof(típus) hívás szolgáltatja. program getmem_freemem; const n=10000; type arr= array[1..n] of longint; var p : pointer; q : ^arr; i : integer; begin { A helyfoglalások elvégzése } getmem(p,n*sizeof(longint)); getmem(q,sizeof(arr)); { A q^ tömb feltöltése } for i:=1 to n do q^[i]:=i; writeln(q^[n div 2]); { A q^ tömb tartalmának átmásolása a p^ területre típuskonverzió felhasználásával.} arr(p^):=q^; { A q^ tömb nullázása } for i:=1 to n do q^[i]:=0; writeln(q^[n div 2]); { A q^ tömb eredeti tartalmának visszamásolása a p^ területről a move eljárás felhasználásával.} move(p^,q^,sizeof(arr)); writeln(q^[n div 2]); { A lefoglalt területek felszabadítása.} freemem(p,n*sizeof(longint)); freemem(q,sizeof(arr)); end. A pointer típusú mutatót legtöbbször dinamikus pufferterületek kijelölésére használjuk ahol a kívánt méret a program futása során kerül meghatározásra, mint ahogy ezt a grafikus ablakterület kezelése szemlélteti. program grafikus_puffer; uses graph; var gd, gm : Integer; p : pointer; meret : word; begin { A grafika bekapcsolása ellenőrzésse. } gd := detect; initgraph(gd, gm, 'c:\tp55'); if graphresult <> grok then halt(1); { A képernyő törlése. } setfillstyle(solidfill,white); bar(0, 0, getmaxx, getmaxy); { A (100,50) - (300,200) terület kezelése.} meret:= magesize(100,50,300,200); { A szükséges méret. } getmem(p, meret); { Helyfoglalás. } getimage(100,100,300,250,P^); { A terület lementése.} setcolor(black); { Rajzolás. } ellipse( 200,175,0,360,100,75); readln; putimage(100,102, P^, NormalPut); { A lementett terület } readln; { visszatöltése. } freemem(p,meret); { Felszabadítás. } { A grafika kikapcsolása. } closegraph; restorecrtmode; end. 12.2.3.3. A mutatókról bővebben Mint ahogy láttuk, a Turbo Pascal-ban egyaránt definiálhatunk típusos és konkrét típus nélküli mutatókat: var ip : ^byte; { típusos } p : pointer; { típus nélküli } Mindkét esetben értelmezettek az alábbi műveletek: - Értékadás: Értékadás a mutató által definiált objektumnak: ip^:=4; Értékadás magának a mutatónak: p :=nil; ip:=p; - Összehasonlítás: Azonos típusú mutatók esetén használhatók az = és a <> műveletek. A nil konstansmutató és a pointer típussal deklarált mutatók bármely más mutató-val összehasonlíthatók. if (ip=p) and (ip<>nil) then halt; - A mutató típusa Turbo Pascal típuskonverzióval megváltoztatható. Ily módon pointer mutatókhoz is rendelhetünk típust. a., Az arr típusú mutató által kijelölt területre longint-ként hivatkozunk: type arr=array[1..4] of byte; var ap:^arr; begin new(ap); longint(ap^):=$12345678; {A fenti értékadás megfelel a } {ap^[1]:=$78;ap^[2]:=$56; ap^[3]:=$34;ap^[4]:=$12;} { értékadások sorozatának } dispose(ap); end. b., A pointer mutatót számára lefoglalt blokkot a tomb típus segítségével integer egységekben érjük el: const n=1000; type tomb=array[1..n] of integer; var p:pointer; i:integer; begin getmem(p,n*sizeof(integer)); for i:=1 to n do tomb(p^)[i]:=i; freemem(p,n*sizeof(integer)); end. - A mutatók megadhatók eljárás és függvény paraméterként, vagy függvény visszatérési értékként egyaránt: program pointer_pelda; var ip:^integer; { Ellenőrzött helyfoglalás } function fnew(meret:word):pointer; var p:pointer; begin if meret>maxavail then fnew:=nil else begin getmem(p,meret); fnew:=p; end; end; procedure fdispose(p:pointer; meret:word); begin freemem(p,meret); end; begin ip:=fnew(sizeof(integer)); if ip=nil then halt(1); {...} fdispose(ip,sizeof(integer)); end. - Az Addr függvény és a @ operátor Az Addr függvény illetve az ennek megfelelő @ operátor segítségével bármely Pascal objektum (függvény, változó, ...) címe lekérdezhető, és tetszőleges típusú mutatóhoz hozzárendelhető. var ip : ^integer; i : integer; begin i :=5; ip:=@i; { vagy ip:=Addr(i); } ip^:=i+6; writeln(i); { A program által kiírt eredmény: 11 } end. - Tetszőleges Pascal objektum címének szegmens és offszet része egyaránt lekérdezhető a Seg és az Ofs függvények hívásával. A MEMORIA.PAS program a futó program néhány jellemző memóriaadatát jeleníti meg: program memoria; var x : Integer; off, segm : Word; ds, cs, ss, sp : Word; p : ^Integer; { max.32 bites egész szám(w) konvertálása pos darab } { jegyet tartalmazó hexadecimális sztringgé } function HexStr(w : longint; pos:byte):string; const hex: array [0..$F] of Char ='0123456789ABCDEF'; var r : record case byte of 0: ( w:longint); 1: ( a:array[1..4] of byte); end; res : string[10]; begin res:=''; repeat r.w:=w; res:=hex[r.a[1] and $0f]+res; w:=w shr 4; dec(pos); until pos<1; hexstr:='$'+res; end; begin x := 30000; off := Ofs(x); segm := Seg(x); Writeln( 'Az x szegmenscíme: ',hexstr(segm,4)); writeln( ' offszetcíme : ',hexstr(off,4) ); p := Ptr( segm, off ); Writeln('A ',hexstr(longint(p),8),' ' címen tárolt egész: ',p^ ); cs := CSeg; ds := DSeg; Writeln; Writeln( 'Kódszegmens CS: ',hexstr(cs,4)); Writeln( 'Adatszegmens DS: ',hexstr(ds,4)); ss := SSeg; sp := SPtr; Writeln( 'Veremszegmens SS: ',hexstr(ss,4)); Writeln( 'Veremmutató SP: ',hexstr(sp,4)); Writeln; end. A program futásának eredménye az alábbiakban látható: Az x szegmenscíme: $099F offszetcíme: $004E A $099F004E címen tárolt egész: 30000 Kódszegmens CS: $08BD Adatszegmens DS: $099F Veremszegmens SS: $09CA Veremmutató SP: $3EFE Külön felhívnánk a figyelmet a hexstr konverziós rutinra, amely a $0..$FFFFFFFF tartományon képes egész számokat hexadecimális sztringgé konvertálni. Ha egy memóriacímet ismerünk szegmens:offszet alakban, akkor erre a címre rámutathatunk egy pointerrel amelyet a Ptr függvény szolgáltat. A példában a billentyűzetpuffer törlésére mutatunk megoldást: procedure clearkbd; var headp,tailp:^word; begin headp:=ptr(0,$41a); tailp:=ptr(0,$41c); headp^:=$1e; tailp^:=$1e; end; 12.2.3.4. Az absolute deklaráció Az előző alfejezetekben a memória elérését mutatók felhasználásával mutattuk be. Az abszolút deklaráció felhasználásával tetszőleges Pascal változót a memória tetszőleges címére helyezhetünk. A deklarációt az alábbi két alakban használhatjuk: var azonosító : típus absolute szegmenscím: offszetcím; vagy var azonosító : típus absolute változó; Az első alak alkalmazása esetén a deklarált változó a memória szegmenscím:offszetcím címén kerül elhelyezésre, míg a második esetben már meglevő változóra definiáljuk rá az új változót. Nézzünk egy-egy példát a két alak felhasználására: a., A színes szöveges képernyőpuffert a screen tömb képernyőterületre való rádefiniálásával érjük el (a 128-nál nagyobb kódú karaktereket pontra cseréljük, illetve az attribútum byte villogás bitjét invertáljuk): type elem =record ch:char; at:byte; end; scrtype=array[1..25,1..80] of elem; var screen :scrtype absolute $b800:0000; s,o :byte; begin for s:=1 to 25 do for o:=1 to 80 do begin iford(screen[s,o].ch)>128 then screen[s,o].ch:='.'; screen[s,o].at:=screen[s,o].at xor $80; end; end. b., A második alak lehetővé teszi különböző típusú változók konvertálását. A példában a pointer <--> longint konverziót használjuk, a színes szöveges képernyő jobb felső sarkában villogó A betű megjelenítésére: var l:longint; p:pointer absolute l; begin l:=$b800009E; char(p^):='A'; inc(l); byte(p^):=$8f; end. 12.2.3.5. A memória és a portok közvetlen elérése A Turbo Pascal rendelkezik öt előredefiniált tömbbel: Mem, MemW, MemL, Port, PortW. Mem[ szegmens:offszet ] MemW[ szegmens:offszet ] MemL[ szegmens:offszet ] A Memx tömbök segítségével tetszőleges memóriacímen található byte (Mem), szó (MemW) illetve duplaszó (MemL) típusú adatelem elérhető. A tömbök indexei eltérnek a szokásos indexektől mivel szegmenscím:offszetcím alakúak. Példaként tekintsük a billentyűzetpuffer törlését, amely egyszerűen elvégezhető a MemW tömb felhasználásával: procedure clearkbd; begin memw[0:$41a]:=$1e; memw[0:$41c]:=$1e; end; {CAPSLOCK billentyű bekapcsolása a $40:$17 címen található } {byte 6.bitjének 1-beállitásával. } Mem[$40:$17] := Mem[$40:$17] OR $40; { Az éjfél óta eltelt idő (1/18 mp) le- kérdezése longint típusú változóba } ticks := MemL[$40:$6C]; { $2F attributumú, $01 kódú karakter el- helyezése a képernyő bal felső arkán } MemW[$B800:2] := $2F21; Port [ portszám ] PortW[ portszám ] A Port és a PortW tömbök segítségével a perifériák közvetlenül programozhatók illetve vezérelhetők. Ebben az esetben az index word típusú és lehetséges értékei IBM PC számítógépen 2..1023. Nem szabad elfelednünk, hogy a portok közvetlen használata potenciális veszély rejt magában, ezért mindig körültekintően kell eljárnunk: { byte olvasása a $64 portról } writeln(Port[$64]); { word írása a $B6 portra:ez a művelet párhuzamosan } { irja a $B6 és a $B7 portokat rendre $22 illetve $11 } { értékekkel. } PortW[$B6] := $1122; 12.2.3.6. Típus nélküli paraméterek használata Nagyon hasznos lehetősége a Turbo Pascal-nak a típus nélküli paraméterek használata az eljárások és a függvények paraméterlistájában. Ha a változó-paraméter típusát elhagyjuk, akkor azt típus nélküli paraméterként kezeli a rendszer. Általában akkor használjuk ezt a lehetőséget, amikor a paraméterek típusa nem lényeges, például memória blokkok másolásakor (blockread, blockwrite, move), vagy más memóriablokkra vonatkozó műveletnél (fillchar). Nézzünk néhány példát a típus nélküli paraméterek felhasználására: a., A swapvar eljárás tetszőleges típusú változók értékének felcserélésére használható: procedure swapvar(var a,b; size:longint); type tp = array [1..maxint] of byte; var v1 : tp absolute a; v2 : tp absolute b; i : longint; tmp: byte; begin for i:=1 to size do begin tmp := v1[i]; v1[i] := v2[i]; v2[i] := tmp; end; end; b., Tetszőleges méretű egész elemeket tartalmazó négyzetes mátrixok összegzése. A $R dírektívával lokálisan letiltjuk az indexhatár ellenőrzését. procedure SumMatr(var a,b,c; n:integer); var x : array[1..1] of integer absolute a; y : array[1..1] of integer absolute b; z : array[1..1] of integer absolute c; i,j : integer; begin {$R-} for i:=0 to n-1 do for j:=1 to n do z[i*n+j]:=x[i*n+j]+y[i*n+j]; {$R+} end; c., A move és a fillchar rutinok hiányzó társa, a memóriaterületek összehasonlítását végző compare eljárás az alábbiakban látható. A hivat-kozáshoz a típuskonverziót használtuk: function compare(var source, dest; size:word):boolean; type barr = array[0..maxint] of byte; var n:integer; begin n:=0; while(n