Управление динамической памятью для пасссивных объектов (в дальнейшем просто динамической памятью) реализуется на основе двух основных процедур (обычно импортируемых из системного модуля):
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 и DEALLOCATE соответственно). Использование 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 в теле процедуры). Например: