28.11.2002
|
čo sa budeme dnes učiť
- ako sa pracuje s dynamickými premennými,
ako sa vytvárajú a ako sa rušia
- ukážeme použite netypového
smerníka a netypových formálnych
parametrov
Dynamické premenné
- mnohé premenné, ktoré sme doteraz
používali boli statické:
- ich veľkosť a adresa v pamäti bola určená
už počas kompilácie
- globálne premenné (premenné
hlavného programu, resp. všetkých unitov)
sa vyhradia v jednom dátovom segmente
- lokálne premenné (aj parametre) podprogramov
sa automaticky vyhradia pri volaní podprogramu
(automaticky sa zrušia pri ukončení podprogramov)
ale ich veľkosť a pozícia v pamäti (t.j.
v systémovom zásobníku) sa určí
počas kompilácie
- statické premenné nemôžu zmeniť
svoju veľkosť a adresu počas behu (run time)
Delphi má mechanizmus na vytváranie
dynamických premenných:
- až počas behu (t.j. pomocou príkazov programu,
t.j. run time) sa vyhradí pre ne pamäť
- tieto premenné môžeme vytvárať
alebo rušiť v ľubovoľnom momente počas behu programu
- premenné sa nevytvárajú ani
v dátovom segmente programu ani v zásobníku
ale v špeciálnom segmente, ktorý je určený
pre všetky dynamické premenné heap (halda)
- už sme pracovali so špeciálnymi prípadmi
dynamických premenných: dynamické
polia (array of), reťazce (string)
a objekty (class)
Dynamické premenné sú prístupné
len pomocou tzv. smerníka (referencia,
adresa, odkaz), t.j.
musíme mať smerníkovú premennú
(premenná, ktorá obsahuje referenciu na inú premennú), pomocou ktorej
budeme mať prístup k danej dynamickej premennej
Smerníkové premenné
- sú premenné, ktoré sa používajú
na prístup k dynamickým (ale niekedy aj
ku statickým) premenným
- smerníky sú často statické premenné
- pri štarte programu majú nedefinovanú
hodnotu
- smerníková premenná zaberá
4 bajty (adresy v počítači sú 4-bajtové)
- každá smerníková premenná
môže adresovať (ukazovať, referencovať) len na
premennú konkrétneho
typu (dynamická premenná môže byť
ľubovoľného typu, napr. číslo, reťazec,
pole, množina, záznam, ...), t.j. ak aj ešte
nie je nedefinovaná, vždy presne vieme typ dynamickej
premennej, na ktorú odkazuje
- pri definovaní typu smerník, môže
byť znak ^ len pred identifikátorom! typu:
type Preal=^real; Pint=^integer; PPoint=^TPoint; ale nie: type Pzaznam=^record x,y:integer end; Parr=^array[1..5]
of byte; ...
- do smerníkovej premennej môžeme
priradiť
- buď smerník už existujúcej
premennej:
var
i:integer; s:^integer; begin s:=@i;
//
adresa existujúcej premennej S^:=7;
- alebo môžeme vytvoriť novú
dynamickú premennú a jej adresu
priradiť do smerníkovej premennej:
var
s:^integer; begin new(s);
//
adresa novovytvorenej dynamickej premennej s^:=7;
- s premennou, na ktorú odkazuje smerník,
môžeme pracovať rovnako ako s "obyčajnou"
premennou: ku smerníkovej premennej pripíšeme
znak ^ (strieška)
- smerníkové premenné môžeme
navzájom priradzovať, resp. porovnávať
na rovnosť alebo nerovnosť jedine ak sú identického
typu
Štandardné procedúry new a dispose
- "zabudne" pôvodnú hodnotu
p
- ak bolo p nedefinované, tak to zrejme
nevadí - ak p odkazovala na dynamickú premennú (a nik
iný), tak k nej strácame prístup
- vyhradí vo voľnej časti pamäti (heap)
veľkosť sizeof(typ)
- väčšinou je to trochu viac (závisí
od organizácie správy pamäti, ktorá
sa stará o heap)
- do p priradí adresu tejto novej
dynamickej premennej
dispose(p:^typ);
- zaradí do voľnej pamäte (heap) premennú
p^
- p má nedefinovanú hodnotu (nemala
by sa ďalej používať)
- všetky smerníky, ktoré odkazovali
na túto istú dynamickú premennú,
majú tiež nedefinovanú hodnotu - dynamickej premennej v heape sa pravdepodobne poškodila
hodnota - už ju nesmieme používať
HEAP = časť pamäti pre všetky dynamické
premenné (závisí od OS - často niekoľko
100 MB)
Konštantný smerník NIL
Niektoré zásady slušného programovania
- snažíme sa, aby všetky smerníkové
premenné mali buď nil alebo skutočne ukazovali
na nejaké premenné (t.j. neboli nedefinované)
- ak p=nil => odkaz p^ hlási známu
chybu Access Violation a preto sa v programe často vyskytuje
otázka
if p=nil, prípadne if p<>nil …
- nikdy neodkazujeme smerníkovou premennou,
o ktorej nie sme 100% presvedčení, že je definovaná
a rôzna od nil (radšej buďme pesimisti a všetko
kontrolujme)
Príklady práce so smerníkmi
dynamická premenná celé číslo:
|
var s:^integer;
begin
new(s); s^:=0;
for i:=1 to 10 do s^:=s^+i;
writeln(t,s^);
dispose(s); s:=nil;
...
|
dynamický záznam:
|
type zaznam=record x,y:integer end;
var z:^zaznam;
begin
new(z); z^.x:=100; z^.y:=200;
with z^ do g.MoveTo(x,y);
inc(z^.x,100); dec(z^.y,50);
g.LineTo(z^.x,z^.y);
...
|
dynamické jednorozmerné pole:
|
type pole=array[1..10] of real;
var p:^pole;
begin
new(p); for i:=1 to 10 do read(t,p^);
for i:=9 downto 1 do p^[i]:=p^[i]+p^[i+1];
...
|
problém s veľkým poľom ako lokálna
premenná:
|
type pole=array[1..1000000] of integer;
procedure test;
var p:pole;
begin
p[1]:=1;
end;
|
- takéto pole chce vzniknúť na zásobníku
počas volania tejto procedúry - systém má
ale problém s tak veľkým poľom
veľké pole ako lokálna premenná:
|
type pole=array[1..1000000] of integer;
var
n:integer;
procedure test;
var
p:^pole;
begin
new(p);
inc(n); p^[1]:=n;
Form1.Memo1.Lines.Add(IntToStr(p^[1]));
// dispose(p);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
while true do test;
end;
|
- ak sa toto veľké pole pred koncom procedúry
neuvoľňuje (dispose), program po niekoľkých
prechodoch spadne na správe "Out of
memory." - môžete takto otestovať veľkosť
systémového zásobníka
...
smerník na objekt:
|
type tt=class a:integer; constructor Create(aa:integer); end;
constructor tt.Create(aa:integer); begin a:=aa; end;
var s:^tt;
begin
new(s); s^:=tt.Create(8);
writeln(t,s^.a);
s^.Free; dispose(s);
end;
|
jednorozmerné pole smerníkov:
|
type
mnozina=set of byte; // 32 bajtov
pole=array [1..1000] of ^mnozina;
// 4000 bajtov -- inak bez ^ by bolo 32000 bajtov
var
data:pole;
i:integer;
begin
for i:=1 to 1000 do new(data[i]);
for i:=1 to 1000 do data[i]^:=[random(256)];
...
for i:=1 to 1000 do dispose(data[i]);
// uvoľnený heap -- môže sa ďalej v tomto projekte používať
end;
|
príklady jedno- a dvoj-rozmerných polí
smerníkov:
|
type
pole=array[1..10] of integer;
Ppole=^pole;
pole2=array[1..20] of pole; // obyčajné 2-rozmerné pole
Ppole2=^pole2;
pole2ppole=array[1..20] of Ppole;
var
a:pole2ppole;
b:Ppole2;
i,j:integer;
begin
for i:=1 to 20 do new(a[i]);
for i:=1 to 20 do
for j:=1 to 10 do
a[i]^[j]:=i+j;
new(b);
for i:=1 to 20 do
for j:=1 to 10 do
b^[i][j]:=i+j;
...
end;
|
smerník na pole:
|
type
spole=array[1..10] of ^integer; // pole smerníkov na integer
Pspole=^spole;
sppole=array[1..20] of Pspole;
Psppole=^sppole;
var
c:sppole;
d:Psppole;
i,j:integer;
begin
for i:=1 to 20 do new(c[i]); // sizeof(c)=20*4
for i:=1 to 20 do // sizeof(c[i])=4
for j:=1 to 10 do begin // sizeof(c[i]^)=10*4
new(c[i]^[j]);
c[i]^[j]^:=i+j;
end;
new(d);
for i:=1 to 20 do new(d^[i]); // sizeof(d)=4; sizeof(d^)=20*4
for i:=1 to 20 do // sizeof(d^[i])=4
for j:=1 to 10 do begin // sizeof(d^[i]^)=10*4
new(d^[i]^[j]);
d^[i]^[j]^:=i+j;
end;
...
end;
|
záznamy, polia a smerníky:
|
type
pole=array[1..10] of integer;
Ppole=^pole;
zazn=record
x:pole;
y:Ppole;
z:array[1..10] of ^integer;
end;
Pzazn=^zazn;
polePzazn=array[1..20] of Pzazn;
PpolePzazn=^polePzazn;
var
a:Pzazn;
b:polePzazn;
c:PpolePzazn;
i,j:integer;
begin
new(a);
for i:=1 to 20 do new(b[i]);
new(c); for i:=1 to 20 do new(c^[i]);
for i:=1 to 20 do begin
for j:=1 to 10 do c^[i]^.x[j]:=i+j;
new(c^[i]^.y);
for j:=1 to 10 do c^[i]^.y^[j]:=i+j;
for j:=1 to 10 do new(c^[i]^.z[j]);
for j:=1 to 10 do c^[i]^.z[j]^:=i+j;
end;
...
end;
|
postupné prečítanie premennej c^[i]^.y^[j]
|
c
c^
c^[i]
c^[i]^
c^[i]^.y
c^[i]^.y^
c^[i]^.y^[j]
|
- je typu PpolePzazn = smerník
- je typu polePzazn = array
- je typu Pzazn = smerník
- je typu zazn = record
- je typu Ppole = smerník
- je typu pole = array
- je typu integer
|
- zápis "smerníkovanej"
premennej môžeme skrátiť: znak ^ vynecháme, ak si ho vedia
Delphi jednoznačne domyslieť, t.j. ak za ^
nasleduje bodka "."
alebo hranatá zátvorka "["
Smerník na smerník
nasledujúci príklad len ilustruje nezvyčajné
použitie smerníkov:
|
type
Pint=^integer;
PPint=^Pint;
PPPint=^PPint;
var
i:Pint;
j:PPint;
K:PPPint;
begin
new(i); i^:=123;
new(j); new(j^); j^^:=345;
new(k); new(k^); new(k^^); k^^^:=567;
...
end;
|
Správa pamäti (Memory management)
- táto správa sa stará o udržiavanie
obsadených a uvoľnených častí dynamickej
pamäti (heap)
- štandardné procedúry: New, Dispose, GetMem,
ReallocMem a FreeMem - využívajú správu
pamäti
- každý vyhradený pamäťový
blok (napr. pomocou New) má dĺžku zaokrúhlenú
na najbližší násobok 4 a obsahuje ešte
4-bajtovú hlavičku - dĺžku bloku a iné
stavové informácie
- správa udržiava tieto dve premenné:
- AllocMemCount - počet pamäťových blokov
- AllocMemSize - dĺžka vyhradených pamäťových
blokov
- funkcia GetHeapStatus vráti ďalšie užitočné
informácie o správe pamäti
- rezervované slovo nil je špeciálna
smerníková konštanta - vnútorne
je reprezentovaná 4 bajtami s hodnotou 0
- smerníkový operátor @premenná
vráti smerník (referenciu - adresu) na
danú premennú (neskôr uvidíme
aj smerník na procedúru) - výsledkom
je smerník typu ^typ, ak je typ typom premennej
- smerníková aritmetika: pomocou
štandardných procedúr inc a
dec môžeme posúvať hodnotu smerníka
o dĺžku typu, na ktorý odkazuje
malá ukážka smerníkovej aritmetiky:
|
var
p:^integer;
a:array[1..10] of integer;
i:integer;
begin
p:=@a[10];
for i:=1 to 10 do begin
p^:=i; dec(p);
end;
for i:=1 to 10 do
Memo1.Lines.Add(IntToStr(a[i]));
end;
|
druhý príklad smerníkovej aritmetiky:
|
var
p1,p2:^integer;
a:array[1..10] of integer;
i:integer;
begin
for i:=1 to 10 do a[i]:=i;
p1:=@a[1]; p2:=@a[10];
for i:=1 to 5 do begin
p1^:=p1^+p2^; p2^:=p1^-p2^; p1^:=p1^-p2^;
inc(p1); dec(p2);
end;
for i:=1 to 10 do
Memo1.Lines.Add(IntToStr(a[i]));
end;
|
všetky doterajšie smerníky boli presne zadaného
typu (^typ) - priraďovanie a referencovanie smerníkových
premenných bolo prísne kontrolované prostredím Delphi
Netypový smerník - typ POINTER
univerzálny smerník (podobne ako nil)
- kompatibilný so všetkými smerníkmi:
môžeme ho priradiť do smerníkovej premennej
ľubovoľného typu a naopak (môže to byť
veľmi nebezpečné - ľahko môžeme stratiť
kontrolu nad smerníkmi)
- nemôžeme pomocou neho pracovať s dynamickou
premennou, na ktorú odkazuje (buď ho priradíme
do typového smerníka, alebo ho pretypujeme)
- keď s ním chceme pracovať, tak buď najprv
do neho priradíme už nejaký "hotový"
smerník na dynamickú premennú alebo
vyhradíme novú dynamickú pamäť
pomocou štandardnej procedúry GetMem(premenná_typu_pointer,dĺžka)
- je to podobné New:
- New(p) === GetMem(p,SizeOf(p^));
- takto vyhradenú pamäť uvoľníme
štandardnou procedúrou FreeMem(premenná_typu_pointer)
- je to podobné Dispose:
- Dispose(p) === FreeMem(p);
- použitie uvidíme pri netypových formálnych
parametroch:
Netypový formálny parameter
= formálny parameter, ktorý nemá
uvedený typ
- v tele procedúry ho môžeme použiť nasledujúcimi
spôsobmi:
- pretypovaním na konkrétny typ
- alebo pomocou direktívy absolute (je to pretypovanie
počas deklarácií)
- alebo poslať ako netypový parameter do inej
procedúry
- napr. štandardná procedúra Move(odkiaľ,
kam, koľko_bajtov)
- alebo zápis, resp. načítanie do/z netypového
súboru – budeme vidieť neskôr
napr. procedúra na výmenu obsahov dvoch
ľubovoľných (rovnako veľkých) premenných:
|
procedure vymen(var a,b; dlzka:integer);
var
t:Pointer;
begin
GetMem(t,dlzka);
Move(a,t^,dlzka); Move(b,a,dlzka); Move(t^,b,dlzka);
FreeMem(t);
end;
|
použitie operátora @ a smerníkovej
aritmetiky inc
|
function dump(var a; dlzka:integer):string;
var
p:^byte;
begin
Result:=''; p:=@a;
while dlzka>0 do begin
Result:=Result+IntToHex(p^,2)+' ';
inc(p);
dec(dlzka);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
s:array[0..10] of char;
i:integer;
begin
s:='Ahoj Delphi';
Label1.Caption:=dump(s,sizeof(s));
i:=12345;
Label2.Caption:=dump(i,sizeof(i));
end;
|
Direktíva ABSOLUTE
- prekrytie premennej iným menom premennej (aj
iného typu) - nejaká časť pamäti
dostane ďalšie nové meno - je to veľmi nebezpečné
iná verzia šestnástkového výpisu
kusu pamäti:
|
function dump2(var a; dlzka:integer):string;
var
p:array[1..100] of byte absolute a;
i:integer;
begin
Result:='';
if dlzka>100 then dlzka:=100;
for i:=1 to dlzka do
Result:=Result+IntToHex(p[i],2)+' ';
end;
|
Dynamické polia
- reprezentované sú smerníkom
na jednorozmerné pole
- deklarácia nealokuje pamäť (mali by sme
priradiť nil)
- SetLength vyhradí pamäť (niečo ako GetMem)
- ak už premenná mala vyhradené nejaké
pole, tak toto sa automaticky uvoľní (niečo ako
FreeMem)
- ak X a Y sú premenné rovnakého
typu dynamické pole, potom X :=Y spôsobí,
že X referencuje na to isté pole ako Y (netreba
alokovať pamäť pre X) - Delphi si teraz pamätá,
že na toto pole sa odkazuje dvomi premennými
a pamäť uvoľní, až keď sa zmenia referencie
oboch polí;
- nepoužívajte procedúry New, GetMem
a pod. a ani operátor ^
v príklade:
|
var
A,B:array of integer;
begin
SetLength(A,4);
A[0]:=1;
B:=A; // teraz sú obe polia v pamäti identické
B[0]:=2; // aj hodnotou A[0] je 2
SetLength(B,3); // teraz sú obe polia v pamäti na rôznych miestach
end;
|
- pri porovnávaní premenných typu
dynamické pole sa porovnávajú ich
referencie a nie nie hodnoty polí
napr.
|
var
A,B:array of integer;
begin
SetLength(A,1);
SetLength(B,1);
A[0]:=2;
B[0]:=2;
if A = B then ...
end;
|
- A=B vráti false ale A[0]=B[0] vráti
true
- na skrátenie dynamického poľa sa môže
použiť aj Copy, napr. A:=Copy(A,5,10);
- premenná typu dynamické pole zaberá
4 bajty = smerník na dynamicky alokované
pole
- buď je to nil alebo smerník na blok pamäti,
ktorý je o 8 bajtov dlhší ako veľkosť
poľa
- 4 bajty použité na počet referencií
- 4 bajty na počet prvkov poľa (Length)
- za tým nasledujú prvky poľa
- viacrozmerné dynamické pole je reprezentované
úplne rovnako -- je to dynamické pole
smerníkov na dynamické polia
Znakové reťazce - String
- sú podobné dynamickým poliam
- tiež sú to smerníky na polia znakov
- podobne sa pamätá aj počet referencií
a aktuálna dĺžka reťazca (Length)
- za posledným znakom v poli je vždy #0 (nedá
sa indexovať) - vďaka tomu je použiteľný aj ako
#0 ukončený reťazec
- prázdny reťazec je uchovaný ako nil
(ale do stringovej premennej sa nemôže priradiť nil)
- nemôžeme používať ani New ani Dispose
ani iné procedúry správy pamäti,
nemôžeme používať ^
teraz by sme už mohli pochopiť tento príklad:
|
procedure TForm1.FormCreate(Sender: TObject);
var
s:string;
begin
s:='ahoj Delphi';
label1.Caption:=dump(s[1],Length(s));
// nefunguje iba dump(s,Length(s)); - s je smerník
end;
|
Znakové reťazce ukončené #0 (null-terminated
strings)
- sú podobné znakovým reťazcom
v C a C++
- niekedy ich treba poznať pri práci so systémom
Windows na nižšej úrovni
- je to postupnosť znakov ukončená znakom #0
- buď v znakom poli - dolná hranica je 0 (napr.
TFileName = array[0..259] of Char;) alebo v dynamickej
pamäti
- preddefinovaný typ PChar (smerník na
postupnosť znakov, t.j. ^char)
pracovať s takýmito reťazcami môžeme
- v štandardných procedúrach a funkciách:
Read, Readln, Str, Val, Write, Writeln, Val, AssignFile,
Rename
- štandardne ako s (dynamickým) poľom znakov
- pomocou veľkej množiny špeciálnych funkcií
pre takéto reťazce, napr.
- StrCat zreťazenie
- StrComp porovnanie
- StrCopy skopírovanie
- StrLen dĺžka reťazca
- môžeme využiť aj smerníkovú aritmetiku
nasledujúci príklad ilustruje prácu
s "null-terminated strings":
|
procedure TForm1.FormCreate(Sender: TObject);
var
p,q:PChar;
begin
GetMem(p,100); // mohli by sme použiť aj StrNew alebo StrAlloc
StrCopy(p,'milujem delphi');
q:=p;
while q^<>#0 do begin
q^:=Upcase(q^);
inc(q); // posun na nasledujúci znak reťazca
end;
Label1.Caption:=p;
Label2.Caption:=dump(p,StrLen(p)+1);
FreeMem(p);
end;
|
Inštancie tried
- každá premenná objekt - t.j. inštancia
nejakej triedy je smerník na dynamicky alokovaný
blok pamäti
- treba na to myslieť pri porovnávaní
aj priraďovaní (napr. g:=Image1.Canvas, alebo
if g.Pen=Form1.Canvas.Pen then ...)
- všetky stavové premenné sú uchované
podobne ako v type záznam
- informácie o metódach sa ukladajú
do tabuľky VMT - virtual method table - je jediná
pre všetky inštancie danej triedy
- okrem metód obsahuje aj iné informácie
o inštancii, napr. informácie o dĺžke, triede
- je jasné, prečo je nezmysel namiesto k:=Kor.Create
použiť k.Create -- k je nedefinované
alebo nil a teda ním nemôžeme
referencovať
|