11.5.3. Процедуры Mark и Release
Механизм действия этих процедур следующий. Пусть переменная P имеет предопределенный тип Pointer, а P1, P2, P3 и P4 объявлены как ссылочные переменные. Пусть текст программы содержит фрагмент
- 203 -
...
New(P1);
New(P2);
Mark(P); { вызов Mark }
New(P3);
New(P4);
...
Release(P); { вызов Release }
...
Перед вызовом процедуры Release куча будет иметь вид, как на рис. 11.4.
Рис. 11. 4
При вызове процедуры Mark в переменную P записалось значение HeapPtr, которое было сразу после размещения P2. Далее были размещены P3 и P4, и указатель HeapPtr передвинулся туда, где он изображен на рис. 11.4.
Если теперь осуществить вызов Release (P), то указатель заполнения кучи HeapPtr переустановится в «позицию», которая была запомнена ранее в указателе P. Куча примет вид, показанный на рис. 11.5.
Рис. 11.5
- 204 -
Действие Release (P) проявилось в том, что куча вернулась в предыдущее состояние, «забыв» обо всех динамических переменных, созданных после выполнения процедуры Mark(P). Теперь уже не надо освобождать ссылки P3 и P4. Их нет, как будто они и не размещались.
Из механизма работы процедуры Release следует, что всю кучу можно освободить одним оператором Release ( HeapOrg ), который приводит ее к исходному пустому состоянию.
Очевидно, что пара процедур Mark/ Release — это очень мощное средство управления кучей. Они позволяют эффективно освобождать память с минимальными усилиями. Однако за это приходится платить гибкостью использования пространства кучи. При их применении освобождение памяти должно производится в порядке, обратном размещению динамических переменных. Так, например, нельзя удалить переменную P2^, не удалив при этом переменные P3^ и P4^. Для более гибкого использования кучи необходимо применять процедуры Dispose и FreeMem.
Вместе с тем не рекомендуется перемежать вызовы процедур Release с вызовами процедур Dispose и FreeMem. Две последние действуют избирательно и могут освобождать блоки памяти в используемой части кучи. Так, если после строки New(P4) в рассмотренном примере поставить вызов Dispose (P1), то перед выполнением Release (P) на месте динамической переменной P1^ будет пусто, и это пустое место могло бы быть потом использовано для размещения других данных. Координаты этого пустого (свободного) блока хранятся в специальном списке свободных блоков. Процедура Release (P) среди всего прочего стирает список свободных блоков, навсегда блокируя тем самым доступ к свободным блокам, находящимся ниже значения указателя P. Об этом надо помнить всегда, иначе можно легко и незаметно заблокировать всю кучу и потерять массу времени на нетривиальное решение вечного вопроса «почему программа не пошла?»