Смекни!
smekni.com

Программирование ориентированное на объекты (стр. 8 из 10)

Управление динамической памятью для пасссивных объектов (в даль­нейшем просто динамической памятью) реализуется на основе двух ос­новных процедур (обычно импортируемых из системного модуля):

PROCEDURE ALLOCATE (VAR A: ADDRESS; N: CARDINAL);

PROCEDURE DEALLOCATE (VAR A: ADDRESS; N: CARDINAL); .

Здесь А - свободный указатель, который укажет на выделенную об­­ласть памяти (элемент хранения размером N байт) при вызове ALLOCATE и получит значение NIL (т.е. никуда не будет указывать) при освобождении этой области "из-под" А путем вызова DEALLOCATE.

Использование ограниченных указателей делает во многих от­но­ше­ни­ях целесообразным использование специальных вызовов: NEW(P) и DISPOSE(P), где VAR P: POINTER TO <Объект>. (NEW и DISPOSE - псе­в­до­процедуры, их вызовы транслируются в вызовы ALLOCATE и DE­AL­LO­CA­TE соответственно). Использование NEW и DISPOSE позволяет из­бежать многих семантических ошибок, связанных с различными значениями N в последовательности вызовов ALLOCATE...DEALLOCATE, определяющей соз­дание/уничтожение одного и того же объекта.

В целом последовательность вызовов NEW...DISPOSE (или соот­вет­ст­­венно ALLOCATE...DEALLOCATE), в общем случае полностью оп­ре­­де­ля­е­мая логикой программиста, порождает ряд проблем, свя­зан­ных с ор­га­­низацией и распределением свободного пространства ди­на­мической па­мяти. Одной из таких проблем является проблема фраг­ментации. Эф­фект фрагментации заключается в том, что рабочая область ди­на­ми­чес­кой памяти "дро­бится" на части - фрагменты раз­лич­ной длины. Какие-то из них "за­няты" - используются про­г­рам­ми­стом под элементы хранения его объ­ектов, какие-то "свободны", при­чем характер че­ре­до­вания сво­бод­ных и занятых фрагментов в общем случае может быть со­вершенно произвольным. Любой запрос программиста на создание но­во­го объекта приводит к тому, что сис­тема управления динамической па­мятью "подбирает" ему фраг­мент, подходящий по размерам. Правила та­кого подбора могут быть различны, но общая закономерность одна: та­кой фрагмент должен иметь размер, не меньший, чем запрашиваемый про­граммистом. Если подходящий фрагмент имеет больший размер, чем требуется, в при­клад­ную программу будет отдана его часть, котоpая те­­пеpь будет pас­сматpиваться системой как занятый фpагмент, а ос­та­­ток ос­та­нет­ся в свободной зоне в качестве свободного фpагмента. При этом проблема фрагментации заключается в том, что эффект "дро­бле­ния" может привести к тому, что в свободной зоне будет на­хо­дить­ся мно­жество "маленьких" разрозненных свободных фрагментов, в со­во­куп­ности составляющих достаточный объем. Тем не менее, не­с­мо­тря на такой объем, запрос программиста на новый элемент памяти мо­жет получить отказ по причине отсутствия целого подходящего эле­мен­та. Ниже приведен фрагмент программы и схема распределения ди­­на­мической памяти, иллюстрирующие эффект фрагментации. При этом для простоты предполагается, что общий объем ди­на­ми­чес­кой памяти составляет 20 байт.

TYPE Треугольник = POINTER TO Фигура_1

Фигура_1 = RECORD

Сторона_1, Сторона_2, Сторона_3:CARDINAL

END;

Четырехугольник = POINTER TO Фигура_2;

Фигура_2 = RECORD

Сторона_1, Сторона_2, Сторона_3, Сторона_4:

CARDINAL

ЕND

VAR T1, T2: Треугольник; М1, М2: Четырехугольник;

BEGIN NEW(T1);... NEW(M1);... NEW(T2);...

DISPOSE(T1);... DISPOSE(T2); NEW(M2);...

┌───────────────────┐ ─┐

│ WORD │ │

├───────────────────┤ │

│ │ > Свободный фрагмент, ранее

├───────────────────┤ │ использованный под

│ │ │ объект Т1^

├───────────────────┤ ─┘─┐

│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │

├───────────────────┤ │

│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │

├───────────────────┤ > Фрагмент, занятый

│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │ под объект М1^

├───────────────────┤ │

│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │

├───────────────────┤ ─┐─┘

│ │ │

├───────────────────┤ │

│ │ > Свободный фрагмент, ранее

├───────────────────┤ │ использованный под

│ │ │ объект Т2^

└───────────────────┘ ─┘

Иллюстрация построена для момента обработки запроса NEW(M2). В этот момент времени в динамической памяти имеются два сво­бо­д­ных фраг­мента общим объемом шесть слов, которых достаточно для вы­­пол­не­ния зап­роса на выделение элемента хранения под объект М2^ (т.е. для объ­екта, на котоpый будет указывать M2), однако фра­г­ментация не поз­­воляет системе выделить память под объект М2^.

Система управления динамической памятью ведет специальный спи­­сок свободных фpагментов - пул памяти. При возвращении какого-либо эле­мента хранения, используемого в прикладной прог­рам­ме, в пул сво­бодной памяти может быть реализовано "скле­и­ва­ние" соседних сво­бод­ных фpагментов в один фpагмент большего объ­ема. Например, если в предыдущей программе изменить пос­ле­до­ва­тель­ность обращений к динамической памяти на приведенную ниже, то описанного выше отказа по памяти не произойдет:

BEGIN NEW(T1);...NEW(T2);...NEW(M1);...

DISPOSE(T1);...DISPOSE(T2);... NEW(M2);...

Здесь при обработке запроса NEW(M2) в пуле динамической па­мя­ти будет находиться один свободный фрагмент объема шесть слов, об­ра­зо­­ван­ный "склеиванием" элементов Т1^ и T2^, выполненным при об­ра­ботке зап­роса DISPOSE(T2). В общем случае вопросы эффективной ре­ализации управления динамической памятью, обеспечивающей ми­ни­мум отказов при ограниченном объеме, составляют отдельную проб­ле­му. Здесь мы только заметим, что с организацией выделения "пер­вого подходящего" фрагмента памяти в программировании свя­зы­ва­ют такие термины как "хип" или "куча", относящиеся скорее к про­фессиональному жаргону, чем к научно-методической тер­ми­но­ло­гии. Тем не менее эти термины до­вольно образно характеризуют прин­ципы организации динамической памяти.

Организация корректной последовательности запросов связана, кро­ме того, как минимум еще с двумя проблемами. На том же жар­го­не их называют проблемы "висячих ссылок" и "мусора", а оп­ре­де­ля­ют они две стороны одной и той же ошибки, заключающейся в некор­ре­кт­ной работе с указателями. Следующий фрагмент программы ил­лю­с­т­ри­рует возникновение таких ошибок (тип "Треугольник" описан выше).

VAR T1, T2:Треугольник;

BEGIN NEW(T1);...T2:=T1;...

DISPOSE(T1); (* T2-"висячая ссылка" *)

............

NEW(T1);...NEW(T2);...

T1:=T2; (* Остался "мусор" *)

Из этого примера понятно, что "висячая ссылка" - это ука­за­тель при­кладной программы, указывающий на свободный фрагмент ди­на­ми­чес­кой памяти. Поскольку этот фрагмент может быть выделен сис­темой по какому-либо запросу другой прикладной программе, Т2 мо­жет открыть дос­туп к "чужим" данным и "разрешить" их ин­тер­пре­тацию как тре­у­голь­ника. Последствия такой интерпретации в об­щем случае непред­ска­зуемы. Заметим, что "висячая" ссылка и "пус­тая" ссылка (имеющая значение NIL, см. pазд.III) являются со­вер­шен­но разными поня­ти­я­ми. "Мусор" - это занятый фрагмент дина­ми­чес­кой памяти, к которому в прикладной программе потерян доступ. В приведенном примере мусором оказался старый треугольник Т1^, на который указывал Т1 до пе­ре­д­виж­ки (установки на Т2). Этот мусор неустраним: программист не име­ет к нему доступа, а система управления "считает" мусор занятым фраг­ментом памяти.

Объединяет эти два вида ошибок одно общее обстоятельство: они не обнаруживаются исполнительной средой. Идентифицировать по­доб­ные ошибки можно только путем тщательной проверки и отладки прог­раммы. И, наконец, по своим возможным влияниям на работу прог­раммы мусор го­раздо "безобиднее" висячей ссылки. Он фак­ти­чес­ки приводит только к увеличенному расходу памяти, в то время как висячая ссылка спо­соб­на при определенных условиях полностью па­рализовать процесс вы­пол­нения программы. В сложных системах "це­на" висячей ссылки может оказаться очень высокой.

Использование автоматической памяти связано с соз­да­ни­ем / унич­то­жением специальных элементов хранения, связанных с актив­ны­ми объ­ектами - действиями или процедурами. Любая процедура тpе­бует для выполнения собственной индивидуальной локальной сре­ды. Подобную сре­ду образуют локальные переменные, объявленные в про­цедуре, фор­маль­ные параметры, элемент хранения адреса воз­вра­та в процедуру, т.е. набор объектов, обеспечивающих выполнение дей­ствий, связанных с процедурой. Необходимость в локальной сре­де возникает только в мо­мент вызова процедуры - момент интер­пре­та­ции объекта процедурного типа. После завершения такой интер­пре­тации необходимость в локальной сре­де исчезает. Таким обра­зом, время жизни локальной среды ог­ра­ни­чи­вается временем от­ра­бот­ки программы, в которой она описана. Со­от­ветственно запрос на создание локальной среды связан с вызовом про­цедуры, а запрос на уничтожение - с окончанием фазы активности объекта (оператор RETURN или END в теле процедуры). Например: