Методы освобождения областей динамически распределяемой памяти
Динамические переменные, сохраняемые в динамически распреде- ляемой области, освобождаются одним из двух следующих способов:
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 -->L--------------------------- Нижняя граница памяти
Рис. 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 -->L--------------------------- Нижняя граница памяти
Рис. 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 -->L--------------------------- Нижняя граница памяти
Рис. 21.4 Создание незанятой области ("дыры") в динамически распределяемой области памяти.
Если в данный момент выполняется процедура New(Ptr3), то это опять приведет к выделению той же области памяти. С другой сторо- ны, выполнение процедуры Dispose(Ptr4) увеличит размер свободного блока, так как Ptr3 и Ptr4 были соседними блоками (см. Рис. 21.5).
HeapEnd -->--------------------------- Верхняя граница ¦ ¦ памяти ¦ ¦ HeapPtr -->+--------------------------+ ¦ содержимое Ptr5^ ¦ Ptr5 -->+--------------------------+ ¦--------------------------¦ ¦--------------------------¦ ¦--------------------------¦ ¦--------------------------¦ +--------------------------+ ¦ содержимое Ptr2^ ¦ Ptr2 -->+--------------------------+ ¦ содержимое Ptr1^ ¦ Ptr1 -->L--------------------------- Нижняя граница памяти
Рис. 21. 5 Увеличение размера незанятого блока памяти.
В конечном итоге выполнение процедуры Dispose(Ptr5) приведет сначала к созданию незанятого блока большего размера, а затем НеаpPtr переместится в более младшие адреса памяти. Поскольку последним допустимым указателем теперь будет Ptr2 (см. Рис. 21 6), то это приведет к действительному освобождению незанятого блока.
HeapEnd -->--------------------------- Верхняя граница ¦ ¦ памяти ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ HeapPtr -->+--------------------------+ ¦ содержимое Ptr2^ ¦ Ptr2 -->+--------------------------+ ¦ содержимое Ptr1^ ¦ Ptr1 -->L--------------------------- Нижняя граница памяти
Рис. 21.7 Освобождение незанятого блока памяти.
Как показано на Рис. 21.7, динамически распределяемая об- ласть памяти теперь находится в том же самом состоянии, в каком она находилась бы после выполнения процедуры Rеlеаsе(P). Однако создаваемые и освобождаемые при таком процессе незанятые блоки отслеживаются для их возможного повторного использования.