Организация автоматической сборки мусора для простых динамических переменных в DelphiИсточник: delphikingdom Максим
Автор: Максим, Королевство Delphi Автоматическая сборка мусора для динамических переменных является неотъемлемой технологической особенностью современных языков программирования - как "раскрученных" типа java или платформы .NET, так и менее известных, например семейства Oberon. Уж не говоря о функциональных языках, где она применяется ещё с 70-х годов. Она позволяет свободно передавать указатели как параметры процедур, не определяя, кто ответственен за освобождение памяти. Особенно это важно при разработке многомодульных сложных систем. Практически наличие автоматической сборки мусора позволяет использовать динамические переменные как переменные обычных простых типов данных, не заботясь об освобождении, а зачастую и о выделении памяти. Автоматическая сборка мусора для динамических переменных не является стандартом Паскаля и, в частности, в Delphi отсутствует. Существует широко известный способ организовать автоматическую сборку мусора для классов с использованием интерфейсов, однако когда речь идёт о небольших динамически выделяемых объёмах данных, для которых инкапсуляция методов не требуется (PItemIDList'ах, например), то создание класса и интерфейса для каждого подобного типа вместо простого типизированного указателя снижает быстродействие программы и усложняет её, и вообще напоминает стрельбу пушкой по воробьям. Столкнувшись с необходимостью организации автоматической сборки мусора для простых динамических переменных (не классов), я пролистал пару учебников по Delphi и провёл поиск в интернете (правда, весьма поверхностный), но решения нигде не нашёл. Так что пришлось придумать способ самому, и если он кому-нибудь окажется полезен, что ж - я буду рад :) Предлагаемый метод основан на том, что в Delphi существует тип данных, для которого реализована автоматическая сборка мусора - это AnsiString. По сути, AnsiString - это указатель на динамически выделяемую область памяти, в которой расположен массив элементов типа char. Кроме этого массива, в выделенной области памяти присутствуют также размер выделенной области памяти (это есть в любой динамически переменной в Delphi) и счётчик количества ссылок на память (это есть только у строк). При присваивании переменной типа AnsiString s1:=s2, счётчик количества ссылок области памяти, на которую указывала переменная s1, уменьшается на 1, затем s1 устанавливается на область памяти, на которую указывает s2, и происходит увеличение счётчика количества ссылок в области s2 на 1. То же самое происходит и при передаче строк как параметров процедур и при возвращении результатов функций строкового типа. Если в ходе этих операций счётчик ссылок какой-то области памяти обнулялся, то она автоматически освобождается. Такой механизм автоматической сборки мусора (в англоязычной литературе известной как Reference Counting) позволяет очень гибко организовать работу со строками в Delphi. В самом деле, все, кто интенсивно использует строки, знают, что их можно в большинстве случаев безопасно использовать как обычные, не динамические, переменные. Никаких утечек памяти или Access Violation'ов при этом не возникает. Другим преимуществом такого метода является тот факт, что строки с одинаковым содержанием не дублируются - вместо этого они указывают на одну область памяти. Это экономит ресурсы и увеличивает быстродействие программы. Для реализации автоматической сборки мусора для указателей любого типа можно использовать явное преобразование данных к типу AnsiString. Однако, для разработки сложных приложений этого не достаточно: нужна ещё сильная типизация данных, чтобы обеспечить надёжность приложений. Итак, организуем, к примеру, тип данных Вектор:Type TVector=record x,y,z:single end; PVector=^TVector; Создадим теперь новый тип данных, который будет являться указателем на TVector с автоматической сборкой мусора: Type APVector=object //aka AutoPointer to Vector :) private Storage:AnsiString; Procedure Allocate; public Procedure Deallocate; Function Ptr:PVector; Function RefCount:integer; end; Procedure APVector.Allocate; begin SetLength(Storage,SizeOf(TVector)); end; Procedure APVector.Deallocate; begin Storage:=''; end; Function APVector.Ptr:PVector; begin if Storage='' then Allocate; result:=PVector(Storage) end; Function APVector.RefCount:integer; begin if Storage='' then result:=0 else result:=integer(pointer(integer(Storage)-8)^); end;Рассмотрим некоторые особенности созданного типа данных:
Перечисленные особенности позволяют считать данный метод надёжным и эффективным способом организации автоматической сборки мусора для простых динамических переменных, в лучших традициях Паскаля. Пример, демонстрирующий удобность использования этой конструкции, находится в приложенном файле. В принципе, в такой объект можно при желании ввести дополнительную функциональность, учитывающую необходимые особенности создаваемого типа данных. При этом полезно будет иметь в виду некоторые тонкости реализации object'ов и строк в Delphi. А именно:
|