Динамикалық жад және сілтемелер


Программада анықталған барлық айнымалылар жедел жадтын берiлгендерінін сегментi деген бiр үздiксiз аумағында орналасады. Сегмент ұзындығы жалпы микропроцессордын архитектурасына байланысты болады және 65536 байттан аспайды, сол себептен үлкен көлемдi массивтермен жұмыс атқарылғанда әртүрлi қиындықтар туындайды. Осындай қиындықтан шығу үшiн динамикалық жадты (немесе үйменi) пайдалану қолайлы деп саналады.

Берiлгендердiн динамикалық жадта орналасуын және онын босатылуын программа орындалу барысында пайдаланады, ал берiлгендердiн статикалық орналастантыруын Delphi ортасынын компиляторы орындайды. Берiлгендердi динамикалық жадқа орналастырғанда алдын ала олардын түрi және ұзындығы белгiсiз, сондықтан оларға аты арқылы қол жеткiзуге болмайды.

8.1 Сiлтемелер

Дербес компьютердiн жедел жады ақпаратты сақтауға арналған қарапайым ұяшықтар – байттар тiзбегiнен тұрады. Әр байттын тiзбектегi өз нөмiрi – адресi болады. Байтқа қол жеткiзу – онын адресi арқылы орындалады. Object Pascal тiлiнде динамикалық жадты басқару үшiн сiлтемелер тәсiлi  қолданылады. Сiлтеменiн мәнi – жадтағы байттын адресi.

Сiлтемелер арқылы динамикалық жадқа Object Pascal тiлiнде қолданылатын кез келген түрдегi берiлгендердi орналастыруға болады. Byte, Char, Shortint, Boolean түрдегi берiлгендер бiр байт орын алады, басқа түрдегiлер – бiрнеше көршi байттарда орналасады, сондықтан сiлтеме тек қана бiрiншi байттын адресiн көрсетедi.

Әдетте сiлтемелер берiлгендердiн бiр түрiмен байланысты болады. Оларды типтiк сiлтемелер деп атайды. Типтiк сiлтеменi анықтау үшiн “ ^ ” белгiсi қолданылады және сiлтеме келесi түрде сиптталады:

Type

   <сiлтемелiк  шаманын  аты >= ^ <кез келген түр>;          немесе

Var

   <сiлтемелiк  шаманын  аты >:^ <кез келген түр>;   

Мысалы,

Var

   P1: ^ integer;           // P1 - бүтiн шамаға сiлтемеле деп анықталған;

   P2: ^ real;                 // P2 - нақты шамаға сiлтеме деп анықталған;

   P3: array[1..100,1..200] of ^ real;

//P3 - нақты шамалардын сiлтемелерiнен құрылған массив деп анықталған.

Программада сiлтемелiк түрдiн анықтамасын келтiрiп, жалпы ережелер бойынша осы түрге жататын айнымалыларды сипттауға болады. Мысалы,

Type

   P: ^intеger;

   PersonPointer=^ PersonRecord;

  PersonRecord=   record

         Name:string[20];

         Job: string [20];

        Next: PersonPointer ;

 End;

Var

    P1,P2:P;

…………………..

Бұл мысалда P – бүтiн шамалардын, ал PersonPointer әлi анықталмаған PersonRecord түрiндегi берiлгендердiн сiлтемелер жиыны. PersonRecord келесi қатарда жазбалар түрiнде сиптталып, онын құрамына кiретiн өрiстерi анықталады: Name және Job жолдар түрiнде сиптталған PersonRecord жазбанын өрiстерi, ал Next жазбанын PersonPointer тегiндегi берiлгендерге сiлтеме түрiнде сиптталған өрiсi.

P1,P2 айнымалылар P типiнде анықталған бүтiн шамаларға сiлтемелер ретiнде қолданылады.

Жалпы ережелер бойынша программадағы кездесетiн барлық айнымалылар, түрлер және тұрақтылар алдын ала сиптталып, сонан кейiн программа денесiнде әртүрлi операторлар құрамында қолданылады. Ал сiлтемелiк түрдегi шамалардын ерекшелiгi – әлi анықталмаған берiлгендер түрiнде сипттау болатындығы. Бiрақ ондай анықтама сонынан келтiрiлуi тиiс.

Болуы мүмкiн сiлтемелiк шамалардын арасындағы жадтын арнайы бос адресi – NIL сiлтемесi деп аталады және ол адрес бойынша ешбiр шама жазылмайды. NIL сiлтемесi кез келген түрдегi сiлтемелiк шамамен тiркеседi деп саналады, яғни NIL сiлтемесiн кез келген түрдегi сiлтемеге тенестiруге болады.

Object Pascal тiлiнде типсiз сiдтемелермен қатар типсiз сiлтемелiк шамалар қолданылады. Осы түрдегi шамаларды анықтау үшiн стандартты Pointer түрi пайдаланылады.

 Type

  <сiлтемелiк  шаманын  аты >= < Pointer >; - типсiз сiлтеме.

Мысалы,

Var

P: Pointer;          

Pointer  түрiндегi сiлтемелер ешбiр типпен байланыспайды, сол себептен оларды программа орындалу барысындағы құрылымы және түрi өзгерiп тұратын шамалар ретiнде қолдануға өте ынғайлы болып келедi.

Жоғарыда айтылғандай, сiлтемелiк шаманын мәнi жадтағы орналасқан мәлiметтердiн адресi. Object Pascal-да мәлiмет алмасу әрекеттерi тек қана бiр түрдегi сiлтемелiк шамалар арасында орындалады. Мысалы, келесi үзiндi берiлген:

Var

P1,P2: ^integer;

P3    : ^real;

PP   : Pointer;

  Begin

   P1:=P2;                  { дұрыс қолданылған оператор }

   P1:=P3;                  { дұрыс қолданылмаған оператор}

 ……………….

  End.         

Мысалда P1:=P2 операторы дұрыс қолданылған, ал P1:=P3 операторын қолдануға болмайды, себебi P1 және P3 әртүрлi тектегi анықталған сiлтемелер. Бұл шектеу типсiз сiлтемелiк шамаларға қатысы жоқ болғандықтан, P3-тiн мәнiн P1-ге меншiктеу үшiн PP типсiз сiлтеменi келесi түрде пайдалануға болады:

  PP:=P3; 

  P1:=PP;

8.2 Динамикалық жадты бөлу және босату

Object Pascal-да кез келген типтелген динамикалық айнымалыларға жадта орын New процедурасы арқылы бөлiнедi. Процедурадағы көрсетiлген параметр типтiк сiлтемелiк шама болуы қажет. Процедура шақырылғанда параметрiнде көрсетiлген айнымалы сыйатын жадтын ен кiшi бос үзiндiсiнiн адресiн табады, ендi осы табылған адрестi сiлтеменiн мәнiне қайтарады, яғни орналастыруға болатын бастапқы динамикалық адреске тенестiредi. Мысалы,

Var

I, j:^ integer;

R : ^real;

Begin

   New(I);   New(R);

…………..

Ендi сiлтеме белгiлi бiр мәндi - адрестi қабылдағаннан кейiн жадтын осы адресi бойынша көрсетiлген тектегi кез келген мәндi орналастыруға болады. Динамикалық жадқа берiлгендердiн мәнiн жазу, меншiктеу операторы келесi түрде келтiрiледi: анықталған сiлтеменiн мәнiнен - адрестен сон сiлтемелiк белгiсi “^“ қойылады, одан кейiн меншiктеу амаланын белгiсi “:= ‘”тұрады, ал он жақта өрнек немесе белгiлi бiр мән жазылады.

Мысалы,

I^ :=20;

    {Жадтын I адресiнен бастап 20 - бүтiн сан орналасады }

R^ :=2*pi;          // Жадтын R адресiнен  бастап

                             //6.28 - нақты сан орналасады

        R^ := sqr(R^)+ I^ -17; { Жадтын R адресiнiн мазмұны 6.28 2+ 20 -17 }

Сонымен, сiлтеме арқылы анықталған берiлгендiн мәнiн көрсету үшiн сiлтемеден кейiн “^ “ танбасын қою керек

Динамикалық жадқа берiлгендердiн мәнiн жазу, меншiктеу операторын қолданғанда жиi кездесетiн қателiктер:

  • Оператор келесi түрде берiлген: R := sqr(R^)+ I^ -17; Келтiрiлген оператор дұрыс жазылмаған, себебi: R жадтын адресi, ал 28 2+ 20-17 деген мән нақты шама, яғни адреспен нақты сан шатасып кеткен.
  • Оператор келесi түрде берiлген R^ := sqr(R)+ I^ -17; Бұл оператордын sqr(R) – өрнегiнде қате кеткен. Адрестi арифметикалық функциянын аргументi ретiнде пайдалануға болмайды.
  • Оператор келесi түрде берiлген: R^ := I -17; R^ - нақты мән, ал I-жадтын адресi. Нақты мәнге адрестi тенестiруге болмайды.

Бұрыннан бөлiнген динамикалық жадты босатуға, яғни үймеге қайтаруға болады. Ол үшiн Dispose процедурасы қолданылады. Мысалы,

     Dispose (I);

     Dispose (R);

Бұл процедура сiлтемелердiн мәнiн өзгертпейдi, тек бұрыннан жадтан бөлiнген аумақ үймеге қайтарылады. Сонымен қатар, босатылған сiлтемеге аталған процедураны қайтадан қолдану орындалу кезенiнiн қатесiнiн пайда болуына себеп болады. Босатылған сiлтемеге NIL деген арнайы сөздi орналастырады, осы әрекеттi белгiлеу деп атайды. Сiлтеменiн белгiленгенiн анықтау үшiн келесi тексеру қолданылады:

Const

PR: ^ Real= Nil;

Begin

………………..

if pR =Nil then

New(pR);

…………………

Dispose(pR);

PR:=Nil;

………………….. end;

Жалпы сiлтеменiн бастапқы мәнi (айнымалылар бөлiмiнде анықталғанда) кез келген болуы мүмкiн. Егер сiлтемеге New процедурасы арқылы бастапқы мәнi анықталмаса, онда осындай жағдайлар жүйелi түрде бақыланбайды және әртүрлi қателiктерге алып келуi мүмкiн.

Жоғарыда айтылғандай New процедурасы тек қана типтелген сiлтемелерге қолданылады. Типсiз сiлтемелермен жұмыс атқару үшiн GetMem және FreeMem процедуралары қолданылады:

             GetMem (P,Size);                // жадты бөлу

             FreeMem(p,Size);               // жадты босату

Мұнда, Р – типсiз сiлтеме, Size – байт өлшемiндегi жадтағы аумақтын ұзындығы.

GetMem және FreeMem процедуралардын қолданылуы келесi талаптарға сай болуын қажет етедi: босатылған және бөлiнген аумақтын адресi, онын ұзындығы бiрдей болуы тиiс.

8.3 Динамикалық жадпен жұмыс атқаратын процедуралар мен функциялар

Динамикалық сiлтемелерге қолданылатын процедура және функцияларды қарастырайық.

Addr (x) x аргументтiн Pointer типтегi адресiн анықтайтын функция. Мұндағы х программанын кез-келген объектiсi (айнымалы, процедура немесе функциянын аты). Қайтарылған адрес кез-келген типтегi сiлтеме болуы мүмкiн.

Dispose (TP) бұрыннан бөлiнген жадтын үзiндiсiн үймеге қайтаратын процедурасы. TP – типтiк шама. Dispose процедурасы босатылған үзiндiге қайтадан қолданылғанда орындалу кезенiнiн қатесi пайда болады.

FreeMem (P,Size) типсiз сiлтемелiк шамаға бұрыннан бөлiнген жадтын үзiндiсiн үймеге қайтаратын процедурасы. P – типсiз сiлтемелiк шама. Size босатылатын немесе үймеге қайтарылатын жадтын ұзындығы.

GetMem (P,Size) типсiз сiлтемелiк шамаға жадтын үзiндiсiн бөлетiн процедура. P типсiз сiлтемелiк шама. Size қажеттi жадтын ұзындығы.

New (TP) типтiк сiлтемелiк айнымалыға жадтын үзiндiсiн бөлетiн процедура. TP – типтiк сiлтемелiк шама. New  процедурасы бiр рет орындалғандағы нәтижесi 65521 байттан аспайды. Егер үймеде бос орын жоқ болса, онда орындалу кезенiнiн қатесi пайда болады.

SizeOf (x) объектiнiн iшкi көрiнiсiнiн ұзындығын (байт) қайтарады. Мұнда x айнымалынын, функциянын немесе түрдiн аты.

Жалпы динамикалық сiлтемелердiн қолданылуы мұқият қадағалауды қажет етедi, себебi сiлтемелiк шамалармен жұмыс атқарылғандағы кеткен көптеген қателердi компилятор сезбеуi мүмкiн. Жиi кететiн қателердiн бiрi салбырап қалған сiлтемелер” мәселесi, олар кейбiр жағдайларда үйменiн басқа бiр аумағынын адресiн көрсетiп тұруы ықтимал.

Төменде осындай бiр жағдайдын үлгiсi келтiрiлген:

Var

P11, p12:^ integer;

Pr:^Real;

Begin             

  New(p11);

   P12:=p11;

   P12^:=2;

  Dispose(p11);

  New(pr);

  Pr^:=2*pi;

  Label1.Caption:= p12^;   // ???

End;

Бұл мысалда алдын ала Label1-дiн мазмұны неге тең екендiгiн айта алмаймыз, себебi р11 сiлтемесiн босатқанда, бұл аумақ үймеге қайтарылды. Сонымен қатар келесi болжамға келуге болады. Егер Windows ортасынан тағы да бiр программа iске қосылмаған болса, онда босатылған 4 байт орын Pr-ге бөлiнiп, онын мазмұны 2* π  = 6, 28318…болуы ықтимал.

Р12 сiлтемесi р11-дiң адресiн сақтағандықтан, ол сiлтеме 2* π мәнi орналасқан аумақты көрсетiп тұр. Сондықтан экран бетiне 626908797 мәні шығып тұрады.

Жалпы Windows ортасынынын динамикалық жадпен жұмыс атқаратын көптеген құралдары бар. Ендi бiрнеше мысал келтiрейiк.

  • CopyMemory – бiр аумақтын мазмұнын басқа аумаққа көшiредi. Аумақтар бiр-бiрiмен қиылыспауға тиiс;
  • FillMemory – аумақты көрсетiлген мәнiмен толтырады;
  • GetProgressHeap – ағымдағы программанын үйме дескрипторын - анықтамасын қайтарады;
  • GetProgressHeaps – ағымдағы барлық программалардын үйме дескрипторларын – анықтамаларын қайтарады;
  • GlobalAlloc – ұзындығы анықталған аумақты бөлу;
  • GlobalFlags – анықталған аумақ туралы мәлiмет қайтару,
  • GlobalFree – бөлiнген аумақты босату және үймеге қайтару;
  • GlobalLock – жадтын аумағын тұрақтандырып, көрсеткiштi онын алғы байтына қайтару;
  • GlobalMemoryStatus – жадтын бос аумағы туралы мәлiмет қайтару;
  • GlobalSize – жадтын көлемiн қайтарады;
  • HeapCreate – программаға жана үйме құру;
  • HeapSize – үйменiн көлемiн қайтару;
  • HeapDestroy – үйменi ортақ жадқа қайтару;
  • ZeroMemory – аумақты нөлмен толтыру.

8.4 Динамикалық жадқа тiзiмдi орналастыру                                       

TList класы арқылы ұзындығы кез келген тізбекті динамикалық жадқа орланастыруды және онын элементтеріне индексі арқылы қол жеткізуді ұйымдастыруға болады. Осындай тізбекті тізім деп атайды және олармен жұмыс істеу массивпен жұмыс істеуiмен ұқсас болып келеді. Ал екеуінін айырмашылығы келесіде: тізімнін элементтер саны программа орындалу барысында өзгеріп отыратындығы және тізімнін құрамындағы берілгендердін тегі әртүрлі болуы.

Шынында, тізім дегеніміз динамикалық жадта орналасқан элементтерге типсіз сілтемелердін массивтері деп саналады. Бұл массивтер үймеде орналасады, сондықтан тізімнін ұзындығын динамикалық түрде өзгертуге болады. Ал тізімнін құрамындағы типсіз сілтемелер кез келген түрдегі берілгендерге сілтеуге мүмкіндік туғызады.

TList класынын бірнеше қасиеттерін ажыратады: Count : Integer – тізімдегі элементтер саны. Ол қасиеттін мәні элемент қосылғанда немесе жойылғанда өзгереді; Capasity: Integer – тізімдегі сілтемелер массивінін ұзындығы. Әрқашанда Capasity > Count. Егер келесі элемент қосылғанда Count = Capasity болса, онда  тізімге 16 элемент автоматты түрде қосылып отырады; Items (Index:Integer): Pointer – көрсеткішті индексіне сәйкес элементке жылжытады. Тізімдегі алғашқы элементтін индексі – 0-ден басталады; List:pPointerList – көрсеткішті тізімнін элементтер массивіне қайтарады.

PPointerList тегі келесі түрде анықталған:

Type

 PPointerList =^ TPointerList;

 TPointerList= array [0.. MaxListSize) of Pointer;

MaxListSize мәні жадтағы бос кеністігімен шектеледі. Count -тізімге орналастырылған элементтер санын анықтайды, ал Capasity – тізімнін ағымдағы ұзындығын көрсетеді. Егер тізімге келесі элемент қосылғанда тізімнін сонына жетсек, онда тізімнін ұзындығы өсіп отырады (Count<5 үшін 4 элементке, 4< Count<8 – 8-ге, Count >7-ге 16 элементке).

Кластын әдістері:

  • Add (Item:Pointer):Integer функциясы Item элементін тізімнін сонына қосады және онын индексін қайтарады.
  • Clear процедурасы тізімді тазалап, барлық элементтерін жояды. Жадты босатпайды. Count және Capasity мәндері 0-ге тен болады.
  • Delete (Index:Integer) процедурасы тізімдегі нөмірі Index элементін жояды. Жойылған элементтен кейін орналасқан элементтер алға жылжыйды.
  • Exchange (Index1,Index2:Integer) процедурасы индекстері Index1 және Index2 элементтерінін орнын ауыстырады.
  • Expand: Tlist функциясы Capasity-ді үлкейтіп, тізімді ұлғайтады.
  • First:Pointer функциясы көрсеткішті тізімнін бірінші элементіне орналастырады.
  • IndexOf (Item:Pointer): integer функциясы тізімдегі Item элементін тауып, онын индексін қайтарады.
  • Insert(Index:Integer; Item:Pointer) процедурасы тізімнін Index позициясына Item элементін қояды, тізімнін қалған элементтерінін индексі Index+1 ден басталады.
  • Last:Pointer функциясы көрсеткішті тізімдегі сонғы элементке орналастырады.
  • Move (Index1, Index2:I nteger) процедурасы индексі Index1 элементті Index2 позициясына орналастырады. Тізімнін Index1 – 1-ден бастап Index2 дейінгі барлық элементтердін индексі 1-ге азаяды.
  • Remove (Item:Pointer): integer функциясы тізімдегі Item элементін тауып, оны жояды.
  • Sort (Compare:TListSortCompare) процедурасы тізімдегі элементтерді Compare функциясы арқылы реттейді.
  • Pack процедурасы тізімді тығыздайды, тізімнін сонындағы бос элементтер жойылады.

Бақылау сұрақтары:

  1. Динамикалық деп жадтын қай бөлігін атайды?
  2. Динамикалық жадтын ұяшықтарына қалай қол жеткізуге болады?
  3. Сілтемелік шамалар түрлері және ол түрлерді қалай жариялайды?
  4. Динамикалық жадқа берілгендерді орналастыру алдында қандай әрекет орындалу керек және берілгендер өнделіп біткеннен кейін жадпен не істеу керек?