Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
РП_31_АСД.pdf
Скачиваний:
164
Добавлен:
23.02.2016
Размер:
2.92 Mб
Скачать

Тема 10. ВИДІЛЕННЯ ТА ЗВІЛЬНЕННЯ ДИНАМІЧНОЇ ПАМ'ЯТІ

У мові Pascal використовуються три методи роботи з пам'яттю, яка динамічно розподіляється:

за допомогою процедур New та Dispose;

за допомогою процедур GetMem та FreeMem;

за допомогою процедур Mark та Release.

Процедура New виділяє область динамічної пам'яті з урахуванням типу покажчика і присвоює адресу пам'яті, що виділена цьому покажчикові. Отже, процедура New створює динамічну змінну того типу, що є базовим для покажчика. Якщо покажчик посилається на тип даних, для якого потрібно пам'яті більше, ніж доступно для розподілу в Heap-області, компілятор генерує помилку Error 203: Heap overflow error (Переповнення купи). Синтаксис виклику процедури New є таким:

New(<ідентифікатор покажчика>);

Процедуру New можна викликати як функцію, що повертає значення покажчика на тип даних, який передається процедурі як параметр. Наприклад, якщо означено посилальний тип indicator=^integer і покажчик ptr: indicator, то наступні два оператора еквівалентні:

New(ptr);

Ptr:= New(indicator);

Використання покажчиків, яким не було надано певного значення за допомогою процедури New або операції отримання адреси @, може призвести до непередбачуваних наслідків.

Процедура Dispose звільняє область динамічної пам'яті, на яку посилається її параметрпокажчик, після чого ця область стає доступною для розподілення між іншими динамічними змінними. Отже, процедура Dispose видаляє динамічну змінну, що адресується її параметромпокажчиком. Синтаксис виклику процедури Dispose такий:

Dispose(<ідентифікатор покажчика>);

Процедура Dispose не змінює значення покажчика, а лише повертає до купи пам'ять, що раніше була з ним зв’язана. Застосування процедури до порожнього покажчика призведе до виникнення помилки Error 204: Invalid pointer operation (Неприпустима операція з покажчиком).

Для роботи з нетипізованими покажчиками можна використовувати процедури GetMem та FreeMem. Процедура GetMem виділяє область динамічної пам'яті заданого обсягу. Початкова адреса області пам'яті, яка буде виділена, запам’ятовується у покажчику, що є параметром процедури. Виклик процедури GetMem має такий синтаксис:

GetMem (<ідентифікатор покажчика>, <обсяг пам'яті>);

Тут (<ідентифікатор покажчика> - змінна типу pointer; (<обсяг пам'яті> - змінна або вираз типу word. Обсяг пам'яті задається в байтах. Обсяг пам'яті, що виділяється для однієї динамічної змінної, не може перевищувати 65 521 байт. Крім того, не можна виділяти ділянок пам'яті, довжина яких перевищує довжину найбільшої вільної ділянки в купі, що її повертає функція MaxAvail. Тому для забезпечення коректності роботи процедури GetMem бажано перед її викликом перевіряти наявність достатньо великої вільної ділянки пам'яті

if MaxAvail>9999 then GetMem(ptr,10000) else write(‘No sufficient memory’);

Загальний обсяг усіх вільних ділянок динамічної пам'яті обчислює функцію MemAvail. Процедура FreeMem звільняє пам'ять, адресовану покажчиком, який є параметром

процедури. Обсяг пам'яті, що звільняється, зазначається як другий параметр процедури FreeMem. Синтаксис виклику процедури наступний:

31

FreeMem (<ідентифікатор покажчика>, <обсяг пам'яті>);

Розподіл динамічної пам'яті за допомогою процедури GetMem потребує її звільнення за допомогою процедури FreeMem. Під час застосування цих процедур до одного й того самого покажчика значення параметрів, що задають обсяг динамічної пам'яті, мають збігатися.

У результаті багатьох викликів процедур New та Dispose, а також GetMem та FreeMem динамічна пам'ять фрагментується. В ній з’являються несуміжні вільні ділянки. Щоб мати можливість звільняти динамічну пам'ять, використовуються процедури Mark та Release.

Синтаксис виклику цих процедур такий:

Mark(<ідентифікатор покажчика>);

Release (<ідентифікатор покажчика>);

Процедура Mark запам’ятовує поточне значення покажчика HeapPrt (що містить адресу початку вільної динамічної пам'яті) у покажчику, що є параметром процедури. Процедура Release звільняє динамічну пам'ять, починаючи від комірки, що адресується параметром процедури, до кінця динамічної пам'яті. Однак виклик процедури Release знищує список усіх вільних фрагментів у динамічній пам'яті, створених викликами процедури Dispose, а також усі динамічні змінні, створені після виклику процедури Mark. Використовувати два різні механізми звільнення динамічної пам'яті (за допомогою процедур Dispose і Release) в межах однієї програми небажано, оскільки доведеться стежити за тим, щоб процедура Dispose не звільняла змінні, що вже були звільнені процедурою Release.

Наступна програма демонструє механізм роботи процедур New та Dispose, а також Mark та Release. Стан динамічної пам'яті під час роботи програми показано на рис.1.

p1

p1

p1

ptr

 

Release(ptr)

p2

p2

 

p3

Dispose(p3)

 

 

 

p4

p4

 

Рис.1. Стан динамічної пам'яті після розподілу (а) та звільнення процедурами Dispose (б) і Release (в)

program Yrok4;

{Працює в Turbo Pascal}

var ptr,p1,p2,p3,p4:^integer;

 

begin

 

 

New(p1);

{виділити пам'ять для цілого числа}

Mark(ptr);

{запам'ятати адресу початку вільної області}

New(p2);

{виділити пам'ять ще для трьох цілих чисел}

New(p3);

New(p4);

Dispose(p3); {звільнити пам'ять від p3^} Release(ptr); {звільнити пам'ять від p2^,p3^,p4} {Dispose(p4); помилка: змінну p3^ вже видалено} readln;

end.

32

Стандартні функції для роботи з адресами

Мова Pascal не дає можливості записувати адреси як константи, а також виконувати арифметичні операції безпосередньо над адресами. Проте ці дії можна виконати за допомогою наступних стандартних функцій:

Функція Addr використовується для отримання адреси змінної або підпрограми і може застосовуватися замість операції визначення адреси @. Формат виклику функції Addr є таким:

<ідентифікатор покажчика>:= Addr(<ім’я змінної або підпрограми>);

Функція Cseg повертає значення типу word, що зберігається в регістрі CS процесора. Коли програма починає роботу, в регістр CS завантажується базис сегмента коду

програми. Функція не має параметрів.

Функція Dseg повертає значення типу word, що зберігається в регістрі DS процесора. Коли програма починає роботу, в регістр DS завантажується базис сегмента даних програми. Функція не має параметрів.

Функція Ofs повертає значення типу word. Це значення дорівнює зміщенню адреси змінної, яка зберігається в сегменті даних, або зміщенню адреси підпрограми, що записана в сегменті коду. Синтаксис виклику функції такий:

<ідентифікатор змінної типу word>:=Ofs(<ім’я змінної або підпрограми>);

Функція Ptr перетворює пару цілих чисел – значення базису сегмента та значення зсунення – на адресу, тобто на значення типу pointer. Виклик функції має такий синтаксис:

<ідентифікатор покажчика>:= Ptr (<Segment>,<Offset>);

Тут <ідентифікатор покажчика> - ідентифікатор змінної типу pointer; (<Segment> - базис сегмента; <Offset> - зсунення елемента у межах сегмента.

33

Тема 11. ПРИКЛАДИ РОБОТИ З ДИНАМІЧНИМИ ЗМІННИМИ

Застосування вказівників дозволило реалізувати особливий вид змінних – динамічних. На відмінну від іншого виду змінних (статичні), які існують протягом всього часу

виконання програми, динамічні змінні можуть створюватися і знищуватися в будь-який момент програми спеціальними програмами.

Статичні змінні - це всі змінні, що оголошені в розділі var незалежно від типу. Ці змінні розміщуються в спеціальному сегменті пам'яті, що називають статичною пам’яттю або програмним стеком (не більше 64 Кб). Ці змінні слідують в порядку оголошення у програмі. Вони з'являються на початку виконання програми і стек звільняється при завершені програми.

Враховуючи, що стек не перевищує 1 сегменту пам'яті, а отже кількість статичних змінних буде обмежена.

Динамічні змінні розміщуються в особливій частині оперативної пам'яті, що називається динамічною пам’яттю, або Heap-областю, або кучею. Під кучу відводиться вся вільна на момент виконання програми пам'ять. Враховуючи, що перших 360 Кб базової пам'яті займає ОП, то максимальний розмір буде 640 Кб.

Реально всі програми, які виконуються паралельно із Pascal-програмою теж частково використовують оперативну пам'ять.

Всі динамічні змінні створюються виключно спеціальними командами, так само вони і знищуються.

Для створення і знищення динамічних змінних є три групи підпрограм:

1.NEW ( var p:point); DISPOSE(var p:pointer).

2.MARK (var p:pointer); RELASE (var p:pointer).

3.GETMEM (var p:pointer; size:longint); FREEMEM (var p:pointer; size:longint).

Перші процедури в кожній групі створюють динамічні змінні; другі – знищують. Використання кожної із груп процедур визначається потребами програми, оскільки результати їх роботи відмінні.

При створенні динамічних змінних процедурою New, в якості фактичного параметра використовується статична змінна вказівного типу.

VAR Pi1:Pint; Ppi1:PPint; Pc:Pchar; Ps:^string; BEGIN New(Pi1); New(PPi1); New(Pc); New(Ps); END.

Тут створюється 4 динамічних змінних, на які є статичні вказівники. Перша динамічна змінна матиме тип integer, друга – вказівний тип на integer, третя – тип char, четверта – тип string.

34

Динамічні змінні розміщуються у пам'яті не неперервним чином, а, починаючи з кожного нового 8 б блоку. Таким чиною створення динамічних змінних малих розмірів є малоефективним, оскільки з користю використовується 1\8, 1\4.

Якщо динамічна змінна створена процедурою New, то знищення можна провести лише відповідною Dispose. Відповідна ділянка кучі звільнюється, а параметр вказівника втрачає своє значення.

При звільненні частини кучі процедурою Dispose з'являються вільні фрагменти пам'яті можливо достатньо великого розміру. Чи будуть при новому створені динамічні змінні розміщуватися в звільнених ділянках? При новому створені, динамічні змінні розміщуються, починаючи з першого 8-мибайтного блоку після останньої динамічної змінної в мові Рascal завжди рівне адресі початку першого 8-мибайтного блоку після останньої динамічної змінної в кучі: Heap ptr.

Всі новостворені динамічні змінні розміщуються, починаючи з адреси Heap ptr. Після кожного нового створення Heap ptr збільшує своє значення.

Друга група підпрограм для створення динамічних змінних оперує лише з динамічними змінними типу pointer і розміром цієї змінної 4 байти.

В якості інформаційної комірки використовувати її малоефективно.

При створені динамічних змінних процедурою Mark(р), її параметр типу pointer отримує значення вказівника Heap ptr. Таким чином змінна р фіксує стан динамічної пам'яті процедурою Mark.

Після фіксації стану, в ній можуть бути створені або знищенні динамічні змінні попередніми процедурами, при цьому вказівник Heap ptr буде отримувати нове значення. Повернутися до зафіксованого раніше процедурою Mark значення Heap ptr можна процедурою Release(p) з цим самим значенням. При цьому не відбувається знищення чи добавлення даних до зафіксованого значення.

Всі динамічні змінні, що знаходяться вище цього значення будуть вважатися знищеними. Виникає питання: що буде в наступній ситуації.

New(p1);

New(p2);

Mark(p);

Dispose(p1);

Dispose(p2);

Release(p);

Така ситуація не відновлює дані, хоча Heap ptr вказує на вершину раніше зайнятої кучі. Утворюються пустоти, в яких не вдасться розмістити нові динамічні змінні.

При розміщенні динамічних змінних у пам'яті, потрібно враховувати, що неможливість чергової операції створення динамічної змінної із-за відсутності місця в кучі, призводить до зупинки програми, тому потрібно контролювати стан динамічної пам'яті на наявності вільного простору. Для цього Pascal має стандартні функції без параметрів.

MemAvail:longint;

MaxAvail:longint;

MemAvail повертає своїм значенням загальний розмір вільної пам'яті в перерахунку на 8-мибайтні блоки. Цей розмір видається в байтах.

35

MaxAvail повертає своїм значенням розмір максимальної неперервної вільної ділянки в кучі в перерахунку на 8-мибайтні блоки. Таким чином при операціях багатократного створення динамічних змінних варто користуватися наступною перевіркою

if MaxAvail>=sizeof(<базовий тип>) then New(…) else writeln(‘немає місця’);

36