Ада-95. Компилятор GNAT
66ac8edd

Управление динамическими объектами


Одним из широко распространенных случев использования объектов контролируемых типов являестя решение проблемы "утечки" памяти при работе с динамическими данными.

Предположим, что у нас есть пакет Lists который содержит описание связанного списка List, и спецификация этого пакета выглядит следующим образом:

package Lists is

type List is private;

Underflow : exception;

procedure Insert_At_Head(Item : in out List; Value : in Integer); procedure Remove_From_Head(Item : in out List; Value : out Integer);

procedure Insert_At_Tail(Item : in out List; Value : in Integer); procedure Remove_From_Tail(Item : in out List; Value : out Integer);

function Full(Item : List) return Boolean; function Empty(Item : List) return Boolean;

private

type List is ... -- полное описание типа . . .

end Lists;



Заметим, что тип связанного списка List, который описан в этом пакете, не является контролируемым типом.

Теперь, рассмотрим следующий пример, использующий описание пакета Lists:

with Lists; use Lists;

procedure Memory_Leaks_Demo is

A, B : List; Result : Integer;

procedure Loose_Memory is

C : List; begin

Insert_At_Head(C, 1); Insert_At_Head(C, 2); end Loose_Memory; -- при попадании в эту точку, -- C выходит за пределы области видимости -- и теряется память ассоциируемая с этим узлом

begin

Insert_At_Head(A, 1); Insert_At_Head(A, 2);

Insert_At_Head(B, 3); Insert_At_Head(B, 4);

B := A; -- B и A указывают на один и тот же список -- все узлы "старого" списка B - теряются

Remove_From_Tail(A, Result); -- изменяет как список A, так и список B

end Memory_Leaks_Demo;

В данном примере наглядно демонстрируются проблемы "утечки" памяти при работе с объектами связанного списка List:

    когда объект связанного списка выходит за пределы области видимости, пространство динамической памяти, выделенное для размещения этого объекта, не восстанавливается

  • в случае выполнения присваивания, осуществляется копирование только указателя на "голову" списка (начало списка), а копирование остальной части списка не выполняется




Рассмотрим вариант модификации пакета Lists

в котором тип связанного списка List является контролируемым. Спецификация пакета будет иметь следующий вид:

with Ada.Finalization;

package Lists is

type List is private;

Underflow : exception;

procedure Insert_At_Head(Item : in out List; Value : in Integer); procedure Remove_From_Head(Item : in out List; Value : out Integer);

procedure Insert_At_Tail(Item : in out List; Value : in Integer); procedure Remove_From_Tail(Item : in out List; Value : out Integer);

function Full(Item :List) return Boolean; function Empty(Item:List) return Boolean;

private

-- обычные описания для списка

type Node; type Ptr is access Node;

type Node is

record

Value : Integer; Next : Ptr; end record;

-- только "голова" списка - "специальная"

type List is new Ada.Finalization.Controlled with

record

Head : Ptr; end record;

-- Initialize не нужна (указатели автоматически устанавливаются в null)

-- procedure Initialize(Item : in out List); procedure Adjust(Item : in out List); procedure Finalize(Item : in out List);

end Lists;

Примечательным фактом является описание подпрограмм Adjust и Finalize

в приватной части спецификации пакета.

Это предотвращает от непосредственной возможности их неуместного вызова клиентами.

Тело пакета Lists (с подробными комментариями) будет иметь вид:

with Unchecked_Deallocation;

package body Lists is

-- подпрограмма освобождения памяти занимаемой узлом procedure Free is new Unchecked_Deallocation(Node, Ptr);

------------------------------------------------------------ -- реализация остальных внутренностей (Insert_At_Head, Remove_From_Head...)

. . .

-------------------------------------------------------------- -- дан указатель на список, подпрограмма будет размещать -- в памяти новый, идентичный первому, список function Copy_List(Item : Ptr) return Ptr is

begin

if Item = null then

return null; else return new Node'(Item.Value, Copy_List(Item.Next)); end if; end Copy_List;

------------------------------------------------------------ -- при присваивании B := A, B будет только переписано содержимым A. -- для связанного списка это подразумевает, что оба, A и B, -- указывают на один и тот же объект. -- теперь, необходимо сделать физическую копию узлов, на которые указывает B, -- и переставить указатель начала списка на начало копии списка, который мы -- только что сделали procedure Adjust(Item : in out List) is

begin

Item.Head := Copy_List(Item.Head); end Adjust;

------------------------------------------------------------ -- освободить всю память, занимаемую узлами списка, -- при разрушении списка procedure Finalize(Item : in out List) is

Upto : Ptr := Item.Head; Temp : Ptr; begin while Upto /= null loop

Temp := Upto; Upto := Upto.Next; Free(Temp); end loop;

Item.Head := null; end Finalize;

end Lists;

<


Ниже представлен пример программы которая использует модифицированную версию пакета Lists

(где тип List является контролируемым).

with Lists; use Lists;

procedure Controlled_Demo is

A : List; -- автоматический вызов Initialize(A);

B : List; -- автоматический вызов Initialize(B);

begin

Insert_At_Head(A, 1); Insert_At_Head(A, 2);

Insert_At_Head(B, 3); Insert_At_Head(B, 4); ------------------------------------------ -- -- A --> | 2 |-->| 1 |--> null -- B --> | 4 |-->| 3 |--> null -- ------------------------------------------

B := A; ------------------------------------------ -- -- Finalize(B); -- освобождение узлов B, до перезаписи -- -- A --> | 2 |-->| 1 |--> null -- B --> null -- -- копирование A в B

-- теперь они _оба_ указывают на один и тот же список -- -- A --> | 2 |-->| 1 |--> null -- B ----^ -- -- Adjust(B); -- B копирует список, на который он в текущий момент -- указывает, и после этого указывает на новый список -- -- A --> | 2 |-->| 1 |--> null -- B --> | 2 |-->| 1 |--> null -- ------------------------------------------

end Controlled_Demo; ------------------------------------------ -- -- Finalize(A), Finalize(B). -- освобождение памяти ассоциируемой с A и B

-- -- A --> null -- B --> null -- ------------------------------------------

Таким образом, использование контролируемых типов предоставляет удобное средство управления жизненным циклом динамических объектов, которое позволяет избавить клиентов типа от необходимости непосредственного управления динамическим распределением памяти.


Содержание раздела