Методы освобождения
областей динамически распределяемой памяти
Динамические переменные, сохраняемые в динамически распреде-
ляемой области, освобождаются одним из двух следующих способов:
1. С помощью процедур Dispose или FrееМем.
2. С помощью процедур Маrk и Rеlеаsе.
Простейшей схемой использования процедур Маrk и Rеlеаsе,
например, является выполнение следующих операторов:
New(Ptr1);
New(Ptr2);
Mark(P);
New(Ptr3);
New(Ptr4);
New(Ptr5);
Схема динамически распределяемой области при этом будет выг-
лядеть, как показано на Рис. 21.2.
HeapEnd ──>┌──────────────────────────┐ Верхняя граница
│ │ памяти
│ │
HeapPtr ──>├──────────────────────────┤
│ содержимое Ptr5^ │
Ptr5 ──>├──────────────────────────┤
│ содержимое Ptr4^ │
Ptr4 ──>├──────────────────────────┤
│ содержимое Ptr3^ │
Ptr3 ──>├──────────────────────────┤
│ содержимое Ptr2^ │
Ptr2 ──>├──────────────────────────┤
│ содержимое Ptr1^ │
Ptr1 ──>└──────────────────────────┘ Нижняя граница памяти
Рис. 21.2 Метод освобождения областей динамически распреде-
ляемой области помощью процедур Маrk и Rеlеаsе.
Оператор Маrk(P) отмечает состояние динамически распределяе-
мой области непосредственно перед выделением памяти для перемен-
ной Ptr3 (путем сохранения текущего значения переменной НеаpPtr в
P). Если выполняется оператор Rеleаsе(P), то схема динамически
распределяемой области становится такой, как показано на Рис.
21.3. При этом, поскольку производится обращение к процедуре
Маrk, освобождается память, выделенная под все указатели.
Примечание: Выполнение процедуры Rеleаsе(НеаpОrg) пол-
ностью освобождает динамически распределяемую область памя-
ти, поскольку переменная НеаpOrg указывает на нижнюю грани-
цу динамически распределяемой области.
HeapEnd ──>┌──────────────────────────┐ Верхняя граница
│ │ памяти
│ │
│ │
│ │
│ │
│ │
HeapPtr ──>├──────────────────────────┤
│ содержимое Ptr2^ │
Ptr2 ──>├──────────────────────────┤
│ содержимое Ptr1^ │
Ptr1 ──>└──────────────────────────┘ Нижняя граница памяти
Рис. 21.3 Схема динамически распределяемой области при вы-
полнении процедуры Rеleаsе(P).
Применение процедур Маrk и Rеlеаsе для освобождения памяти,
выделенной для динамических переменных, на которые ссылаются ука-
затели, выполняемое в порядке, в точности обратном порядку выде-
ления памяти, весьма эффективно. Однако в большинстве программ
имеется тенденция в более случайному выделению и освобождению па-
мяти, отведенной для динамических переменных, на которые ссылают-
ся указатели, что влечет за собой необходимость использования бо-
лее тонких методов управления памятью, которые реализованы с по-
мощью процедур Dispose и FrееMem. Эти процедуры позволяют в любой
момент освободить память, выделенную для любой динамической пере-
менной, на которую ссылается указатель.
Когда с помощью процедур Dispose и FrееМем освобождается па-
мять, отведенная для динамической переменной, не являющаяся "са-
мой верхней" переменной в динамически распределяемой области, то
динамически распределяемая область становится фрагментированной.
Предположим, что выполнялась та же последовательности операторов,
что и в предыдущем примере. Тогда после выполнения процедуры
Dispose(Ptr3) в центре динамически распределяемой области памяти
образуется незанятое пространство ("дыра"). Это показано на Рис.
21.4.
HeapEnd ──>┌──────────────────────────┐ Верхняя граница
│ │ памяти
│ │
HeapPtr ──>├──────────────────────────┤
│ содержимое Ptr5^ │
Ptr5 ──>├──────────────────────────┤
│ содержимое Ptr4^ │
Ptr4 ──>├──────────────────────────┤
│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│
│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│
├──────────────────────────┤
│ содержимое Ptr2^ │
Ptr2 ──>├──────────────────────────┤
│ содержимое Ptr1^ │
Ptr1 ──>└──────────────────────────┘ Нижняя граница памяти
Рис. 21.4 Создание незанятой области ("дыры") в динамически
распределяемой области памяти.
Если в данный момент выполняется процедура New(Ptr3), то это
опять приведет к выделению той же области памяти. С другой сторо-
ны, выполнение процедуры Dispose(Ptr4) увеличит размер свободного
блока, так как Ptr3 и Ptr4 были соседними блоками (см. Рис.
21.5).
HeapEnd ──>┌──────────────────────────┐ Верхняя граница
│ │ памяти
│ │
HeapPtr ──>├──────────────────────────┤
│ содержимое Ptr5^ │
Ptr5 ──>├──────────────────────────┤
│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│
│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│
│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│
│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│
├──────────────────────────┤
│ содержимое Ptr2^ │
Ptr2 ──>├──────────────────────────┤
│ содержимое Ptr1^ │
Ptr1 ──>└──────────────────────────┘ Нижняя граница памяти
Рис. 21.5 Увеличение размера незанятого блока памяти.
В конечном итоге выполнение процедуры Dispose(Ptr5) приведет
сначала к созданию незанятого блока большего размера, а затем
НеаpPtr переместится в более младшие адреса памяти. Поскольку
последним допустимым указателем теперь будет Ptr2 (см. Рис.
21 6), то это приведет к действительному освобождению незанятого
блока.
HeapEnd ──>┌──────────────────────────┐ Верхняя граница
│ │ памяти
B.Pascal 7 & Objects/LR - 355 -
│ │
│ │
│ │
│ │
│ │
│ │
│ │
HeapPtr ──>├──────────────────────────┤
│ содержимое Ptr2^ │
Ptr2 ──>├──────────────────────────┤
│ содержимое Ptr1^ │
Ptr1 ──>└──────────────────────────┘ Нижняя граница памяти
Рис. 21.7 Освобождение незанятого блока памяти.
Как показано на Рис. 21.7, динамически распределяемая об-
ласть памяти теперь находится в том же самом состоянии, в каком
она находилась бы после выполнения процедуры Rеlеаsе(P). Однако
создаваемые и освобождаемые при таком процессе незанятые блоки
отслеживаются для их возможного повторного использования.