Copyright (C) А.Гавва V-0.4w май 2004

12. Ссылочные типы (указатели)

До настоящего момента мы предполагали, что структуры данных и их размещение в пространстве памяти строго фиксировано и не изменно, то есть статично. Поэтому, такие данные, как правило, называют статическими. При этом, следует заметить, что доступ к статическим данным всегда осуществляется непосредственно к месту их физического размещения в памяти, то есть, используется механизм непосредственного доступа к данным.

Для повышения эффективности в современных системах сбора и обработки информации часто требуется использование данных структура которых может изменяться в процессе работы программы (списки, деревья, графы...). Такие данные, как правило, называют динамическими данными или данными с динамической структурой. Для того, чтобы обеспечить эффективную работу с динамическими данными механизм непосредственного доступа как правило не приемлем. Для этого необходим механизм при котором доступ к реальным данным осуществляется через адресные значения, определяющие расположение реальных данных в пространстве памяти, и предоставляется возможность манипулирования такими адресными значениями. Такой механизм доступа к данным называют механизмом косвенного доступа.

Многие современные языки программирования, в качестве механизма косвенного доступа к данным, используют указатели, которые позволяют манипулировать адресами размещаемых в пространстве памяти объектов. Не смотря на то, что указатели являются достаточно эффективным средством работы с динамическими данными, непосредственная работа с адресами и адресной арифметикой часто является источником ошибок которые трудно обнаружить. Известным обладателем подобной проблемы является семейство языков C/C++.

В качестве механизма косвенного доступа к данным, Ада предлагает концепцию ссылочных типов, использование которых во многом подобно традиционному использованию указателей. При этом ссылочные типы Ады обладают следующими характерными особенностями:

Из этого следует, что использование ссылочных типов Ады более безопасно и повышает общую надежность создаваемого программного обеспечения.

Примечательно, что в литературе не редко встречаются случаи когда официальная терминология нарушается и ссылочные типы Ады называют указателями. Как правило, это делается с целью обеспечить более традиционное изложение материала.

Все описания ссылочных типов Ады можно условно разделить на три вида:

При этом, следует заметить, что последние два вида введены стандартом ada.

12.1 Ссылочные типы для динамической памяти

Ссылочные типы для динамической памяти известны со времен стандарта Ada-83 и благополучно поддерживаются в стандарте ada. Они могут быть использованы для ссылок на объекты размещаемые в области динамической памяти, которую также часто называют пулом динамической памяти или кучей.

12.1.1 Элементарные сведения: описание, создание, инициализация

Предположим, что у нас есть следующие описания:

    
    
    type Person_Name is new String (1 .. 4);
    type Person_Age is Integer range 1 .. 150;
    
    type Person is
        record
            Name : Person_Name;
            Age  : Person_Age;
        end record;
    

Предположим также, что нам необходимо разместить экземпляр объекта типа Person в области динамической памяти. Для этого нам надо описать ссылочный тип, значения которого будут ссылаться на значения (объекты) типа Person и переменную этого ссылочного типа (заметим, что все ссылочные типы Ады описываются с помощью использования зарезервированного слова access):

    
    
    type Person_Ptr is access Person; -- описание ссылочного типа
    Someone : Person_Ptr;             -- переменная (объект) ссылочного типа
    

Для индикации того, что переменная ссылочного типа (указатель) не указывает ни на один объект, Ада использует специальное значение null (это подобно тому, что используется в языке Паскаль). По умолчанию, объект ссылочного типа всегда инициализируется в null. Таким образом, после приведенных выше описаний, переменная Someone имеет значение null.

Теперь, чтобы создать объект типа Person в области динамической памяти и присвоить соответствующее ссылочное значение переменной Someone необходимо выполнить следующее:

    
    
    Someone := new Person;
    

Примечательно, что созданный таким образом в области динамической памяти объект типа Person - никак не инициализирован. Таким образом производится простое распределение пространства, которое необходимо для размещения объекта типа Person, в области динамической памяти.

После создания объекта в области динамической памяти, мы можем проинициализировать индивидуальные поля записи типа Person, на которую ссылается переменная Someone, следующим образом:

    
    
    Someone.Name := "Fred";
    Someone.Age  := 33;
    

Следует заметить, что при таком обращении к объекту в динамической области памяти производится неявная расшифровка ссылки, а внешний вид кода подобен тому, что используется при обращении к статическому объекту.

Для того, чтобы обратиться ко всему содержимому объекта, на который указывает значение ссылочного объекта (указателя), Ада использует зарезервированное слово all. Кроме того, в подобных случаях, используются квалифицированные (указывающие имя типа) агрегаты:

    
    
    Someone.all := Person'("Fred", 33);        -- вариант с позиционным агрегатом
    
    Someone.all := Person'( Name => "Fred";    -- вариант с именованным агрегатом
                            Age  => 33      );
    

Оба варианта, показанные в примере, выполняют абсолютно одинаковые действия, и их результат ничем не отличается от показанного ранее примера инициализации путем обращения к индивидуальным полям записи типа Person. Однако, стоит обратить внимание на то, что использование агрегатов для инициализации всего объекта сразу - более удобно, а вариант с именованным агрегатом имеет лучшую читабельность (по сравнению с позиционным агрегатом), а это важно при работе со сложными структурами данных.

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

    
    
    Someone := new Person'("Fred", 33);        -- вариант с позиционным агрегатом
    
    Someone := new Person'( Name => "Fred";    -- вариант с именованным агрегатом
                            Age  => 33      );
    

Чтобы облегчить понимание синтаксиса, который используется в языке Ада при работе со ссылочными типами, для тех кто знаком с такими языками программирования как Си и Паскаль, предлагается следующая сравнительная таблица:

Таблица сравнения синтаксиса указателей/ссылочных типов
в языках Паскаль, Си и Ада:
  Паскаль C Ада
Доступ к полям указываемого
объекта
a^.fieldname *a.fieldname
a->fieldname
a.fieldname
Копирование указателя b := a; b = a; b := a;
Копирование указываемого
обекта
b^ := a^; *b = *a; b.all := a.all

12.1.2 Структуры данных со ссылками на себя

В предыдущих примерах мы рассмотрели то как размещать обычные типы данных в динамической области памяти и выполнять их инициализацию. Однако, для того чтобы строить сложные динамические структуры данных (списки, деревья, графы...) необходима возможность описания структур данных которые могут ссылаться сами на себя, то есть требуется создание ссылки на структуру данных, которая еще не существует (не описана).

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

    
    
    type Element;                 -- неполное описание типа
    
    type Element_Ptr is access Element;
    
    type Element is               -- полное описание типа
        record
            Value : Integer;
            Next  : Element_Ptr;
        end record;
    
    Head_Element : Element_Ptr;   -- переменная которая будет началом списка
    

Теперь мы можем построить простой связанный список, началом которого будет переменная Head_Element, следующим образом:

    
    
    Head_Element := new Element'(
                          Value => 1, 
                          Next  => (new Element'(
                                          Value => 2, 
                                          Next  => (new Element'(
                                                          Value => 3, 
                                                          Next  => null)))));
    

Данный список содержит всего три элемента (узла). Следует обратить внимание на то, что в последнем элементе списка ссылка Next имеет значение null.

12.1.3 Освобождение пространства динамической памяти

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

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

Следует заметить, что стандарт языка Ада не определяет более четких требований и правил для алгоритмов библиотеки времени выполнения. Поэтому, реальные алгоритмы будут определяться реализацией конкретного компилятора и его библиотек поддержки. Следовательно, для уточнения этих сведений необходимо обратиться к документации на используемый компилятор, и здесь этот способ рассматриваться не будет.

Если вам необходимо освободить память (подобно тому как это делает системный вызов free в UNIX), то вы можете конкретизировать настраиваемую процедуру Ada.Unchecked_Deallocation. Эта процедура называется непроверяемой (unchecked) поскольку компилятор не осуществляет проверку отсутствия ссылок на освобождаемый объект. Таким образом, выполнение этой процедуры может привести к появлению "висячих" ссылок.

    
    
    generic
        type Object(<>) is limited private;
        type Name is access Object;
    
    procedure Ada.Unchecked_Deallocation(X : in out Name);
    pragma Convention(Intrinsic, Ada.Unchecked_Deallocatoin);
    

Для показанного ранее ссылочного типа Element_Ptr, значения которого ссылаются на объекты типа Element это может быть конкретизировано следующим образом:

    
    
    procedure Free is new Ada.Unchecked_Deallocation(Object => Element,
                                                     Name   => Element_Ptr);
    

Теперь, можно написать рекурсивную процедуру Free_List, которая будет удалять список, состоящий из объектов типа Element. Начало списка будет указываться параметром процедуры Free_List, а для непосредственного удаления объекта типа Element процедура Free_List будет использовать процедуру Free:

    
    
    with  Free;
    
    procedure Free_List (List_Head: in out Element) is
    begin
        if  List_Head.Next /= null
            Free_List(List_Head.Next);  -- рекурсивный вызов
        end if;
        
        Free(List_Head);
    end Free_List;
    

В результате, для удаления показанного ранее списка объектов типа Element начало которого указывается переменной Head_Element можно выполнить следующее:

    
    
        . . .
    Free_List(Head_Element);
        . . .
    

Следует напомнить, что в рассмотренном нами списке для последнего элемента (узла) значение ссылки Next было равно null.

При описании ссылочного типа Element_Ptr, может быть использована директива компилятора Controlled:

    
    
        . . .
    type Element_Ptr is access Element;
    pragma Controlled(Element_Ptr);
        . . .
    

В этом случае, компилятор будет проинформирован о том, что задачу освобождения распределенного в пуле динамической памяти пространства, для объектов на которые ссылаются значения ссылочного типа Element_Ptr, взял на себя программист.

12.1.4 Пулы динамической памяти

Обычно, все объекты ссылочного типа определенные пользователем, используют один, общий для всей программы, пул динамической памяти. Однако, согласно стандарта Ada95, допускается определение пользовательского пула динамической памяти из которого будет производиться распределение памяти для динамически создаваемых объектов. Такой пул могут использовать различные объекты ссылочного типа.

Пользователь может описать свой собственный тип пула динамической памяти, используя абстрактный тип Root_Storage_Pool описанный в пакете System.Storage_Pools, и после этого ассоциировать его с объектом ссылочного типа используя атрибут 'Storage_Pool. Например:

    
    
    Pool_Object : Some_Storage_Pool_Type;
    
    type T is access <какой-нибудь_тип>;
    for T'Storage_Pool use Pool_Object;
    

Применение пулов динамической памяти определяемых пользователем обусловлено, как правило, достаточно специфическими требованиями решаемой задачи.

Например, достаточно распространенными являются случаи, когда, при использовании единого пула, интенсивное распределение/удаление динамических объектов с различными размерами приводит к чрезмерной фрагментации единого пула динамической памяти. В результате этого, может возникнуть ситуация когда невозможно распределить непрерывный блок динамической памяти требуемого размера, для размещения какого-либо динамического объекта, при фактическом наличии достаточного общего объема свободной динамической памяти. Чтобы предотвратить возникновение подобной ситуации, можно использовать различные пулы динамической памяти для объектов с различными размерами, исключая, таким образом, возможность фрагментации пулов динамической памяти.

Другими примерами случаев использования пулов динамической памяти определяемых пользователем могут служить: необходимость распределения динамической памяти для объектов размер которых значительно меньше чем минимальный размер памяти, который распределяется для размещения объекта в общем пуле динамической памяти программы (иначе - в пуле по-умолчанию); требование приложения реального времени обеспечить фиксированное время выполнения операций распределения/освобождения пространства динамической памяти.

12.1.5 Проблемы обусловленные применением ссылочных типов

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

Рассмотрим следующий пример:

    
    
    declare
    
        type Person_Name is new String (1 .. 4);
        type Person_Age is Integer range 1 .. 150;
    
        type Person is
            record
                Name : Person_Name;
                Age  : Person_Age;
            end record;
    
        X : Person_Ptr := new Person'("Fred", 27);
        Y : Person_Ptr := new Person'("Anna", 20);
    
    begin
    
        X := Y;                        -- *****
        Y.all := Person'("Sue ", 34);
        Put(X.Name);
    
    end;
    

В этом примере, основной интерес для нас представляет строка помеченная звездочками. В ней ссылочному объекту X присваивается значение ссылочного объекта Y, а не значение объекта, на который ссылается Y. После этого оба ссылочных объекта, - и X, и Y, - ссылаются на один и тот же объект, размещенный в области динамической памяти.

Первым интересным моментом является то, что теперь изменение объекта на который ссылается переменная Y будет неявно изменять объект на который ссылается переменная X (такое неявное изменение часто называют "побочным эффектом"). Поэтому в результате выполнения кода этого примера будет выводиться строка "Sue ". Следует заметить, что при разработке реальных программ, работающих со ссылочными типами, необходимо уделять должное внимание эффектам подобного вида, поскольку они могут быть первопричиной странного поведения программы, а в некоторых случаях могут вызвать аварийное завершение работы программы.

Вторым интересным моментом является то, что после выполнения присваивания ссылочному объекту X значения ссылочного объекта Y, теряется ссылка на объект, на который до присваивания ссылался ссылочный объект X. При этом, сам объект продожает благополучно располагаться в области динамической памяти (такой эффект называют "утечкой памяти"). Следует заметить, что при интенсивном использовании ссылочных типов, утечка памяти может привести к тому, что все доступное пространство области динамической памяти будет исчерпано. После этого, любая попытка разместить какой-либо объект в области динамической памяти приведет к ошибке Storage_Error.

12.2 Обобщенные ссылочные типы

Рассмотренные нами ссылочные типы могут быть использованы только как средство косвенного доступа к объектам которые размещаются в области динамической памяти. Однако, бывают случаи когда необходимо использование механизма косвенного доступа для статического объекта, то есть, необходимо иметь возможность обратиться к содержимому статического объекта используя значение ссылочного типа.

Ада (согласно стандарта Ada95) предоставляет возможность описывать статические объекты (переменные или константы) так, чтобы они являлись косвенно доступными, а также предоставляет возможность описывать ссылочные типы, так, чтобы они могли ссылаться не только на объекты размещенные в пуле динамической памяти, но и на статические объекты (которые были описаны как косвенно доступные).

В этом случае, статические объекты (переменные или константы) описываются с использованием зарезервированного слова aliased, которое указывает, что данный статический объект является косвенно доступным. Например:

    
    
    Int_Var   : aliased Integer;
    Int_Const : aliased constant Integer := 0;
    

Чтобы получить ссылочные значения для статических объектов которые допускают использование косвенного доступа необходимо использовать атрибут 'Access.

Чтобы предоставить возможность ссылочному типу ссылаться на косвенно доступные статические объекты, ссылочный тип описывается с использованием зарезервированного слова all или зарезервированного слова constant. Заметим, что такие ссылочные типы называют обобщенными ссылочными типами (general access types), и они также могут быть использованы для доступа к объектам которые размещены в пуле динамической памяти. Например:

    
    
    type Int_Var_Ptr    is  access all Integer;
    type Int_Const_Ptr  is  access constant Integer;
    

При этом, если при описании ссылочного типа использовано зарезервированное слово all, то такой ссылочный тип предоставляет доступ который позволяет осуществлять как чтение, так и запись содержимого объекта на который ссылается значение этого ссылочного типа. Следовательно, такое описание используется для получения ссылок на статические переменные.

Если при описании ссылочного типа использовано зарезервированное слово constant, то такой ссылочный тип предоставляет доступ который позволяет осуществлять только чтение содержимого объекта на который ссылается значение этого ссылочного типа. Следовательно, такое описание чаще всего используется для получения ссылок на константы и в более редких случаях для получения ссылок на переменные.

Рассмотрим следующий пример:

    
    
    procedure General_Access_Demo is
    
        type Int_Var_Ptr_Type is access all Integer;
        A : aliased Integer;
        X, Y : Int_Var_Ptr_Type;
    
    begin
        X := A'Access;
        Y := new Integer;
    end General_Access_Demo;
    

Здесь, переменная A имеет тип Integer и является косвенно доступной. Переменные X и Y имеют тип Int_Var_Ptr_Type который является обобщенным ссылочным типом. В результате, внутри процедуры, переменная X ссылается на статическую переменную A, ссылка на которую получена с помощью атрибута 'Access. Переменная Y ссылается на объект типа Integer который размещен в области динамической памяти.

Ада позволяет использовать обобщенные ссылочные типы для формирования ссылок на отдельные элементы внутри составных типов данных (таких как массив и/или запись), например:

    
    
    type  Array_Type  is array (Positive range < >) of aliased Integer;
    
    type  Record_Type is
        record
            A_Int_Var : aliased Integer;
            Int_Var   : Integer;
        end record;
    
    type Int_Var_Ptr_Type is access all Integer;
    

В данном случае тип Array_Type - это массив, состоящий из косвенно доступных элементов типа Integer, а тип Record_Type - это запись, у которой поля A_Int_Var и Int_Var имеют тип Integer, причем поле A_Int_Var косвенно доступно, а поле Int_Var - нет. Таким образом, значения типа Int_Var_Ptr_Type могут ссылаться на индивидуальные элементы массивов, принадлежащих к типу Array_Type, и поле A_Int_Var записей, принадлежащих к типу Record_Type.

Интересным примером использования обобщенных ссылочных типов может служить следующее схематическое описание пакета:

    
    
    package Message_Services is
        type Message_Code_Type is range 0..100;
    
        subtype Message is String;
    
        function Get_Message(Message_Code: Message_Code_Type) return Message;
    
        pragma Inline(Get_Message);
    end Message_Services;
    
    
    
    package body Message_Services is
        type Message_Handle is access constant Message;
    
        Message_0: aliased constant Message := "OK";
        Message_1: aliased constant Message := "Up";
        Message_2: aliased constant Message := "Shutdown";
        Message_3: aliased constant Message := "Shutup";
            . . .
    
        Message_Table: array (Message_Code_Type) of
            Message_Handle :=
                (0 => Message_0'Access,
                 1 => Message_1'Access,
                 2 => Message_2'Access,
                 3 => Message_3'Access,
                    . . .
                );
    
        function Get_Message(Message_Code: Message_Code_Type)
            return Message is
        begin
            return Message_Table(Message_Code).all;
        end Get_Message;
    end Message_Services;

В данном случае, достаточно элегантным приемом является использование массива Message_Table, который представляет таблицу ссылок на строковые константы переменной длины. Следует также заметить, что при этом не производится никакого распределения пространства в области динамической памяти.

12.2.1 Правила области видимости для обобщенных ссылочных типов

В целях обеспечения надежности, на использование обобщенных ссылочных типов накладываются некоторые дополнительные ограничения. Так, область видимости косвенно доступного объекта, на который будет ссылаться значение переменной обобщенного ссылочного типа T, не должна быть глубже чем область видимости переменной обобщенного ссылочного типа T. Это подразумевает, что следующий пример - не корректен, и, следовательно, будет отвергнут компилятором:

    
    
    procedure Illegal is            -- внешняя область видимости описаний
    
        type Integer_Access is access all Integer;
        Integer_Ptr : Integer_Access;
    
    begin
            . . .
        declare                     -- внутренняя область видимости описаний
            Integer_Variable : aliased Integer;
        begin
            Integer_Ptr := Integer_Variable'Access;         -- это не корректно!!!
        end;                        -- завершение области видимости
                                    -- переменной Integer_Variable
    
        Integer_Ptr.all := Integer_Ptr.all + 1;             -- сюрприз!
                                    -- переменная Integer_Variable
                                    --   больше не существует!
    
    end Illegal;                    -- завершение области видимости
                                    --   для Integer_Access
    

Смысл примера заключается в следующем. Во внутреннем блоке, переменной IA ссылочного типа Integer_Access присваивается значение которое ссылается на переменную IVar. При завершении внутреннего блока, переменная IVar прекращает свое существование. Следовательно, в следующей инструкции присваивания, переменная IA ссылается на не существующую переменную. Такая ситуация известна как "проблема висячих указателей".

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

Бывают случаи когда необходимо нарушить строгость данного ограничения. Тогда, для получения ссылочного значения, вместо атрибута 'Access можно использовать атрибут 'Unchecked_Access, который позволяет получить ссылочное значение без осуществления проверки правил доступа:

    
    
    procedure Legal_But_Stupid is
    
        type Integer_Access is access all Integer;
        IA : Integer_Access;
    
    begin
            . . .
        declare
            IVar : aliased Integer;
        begin
            IA := IVar'Unchecked_Access;  -- это не надежно!!!
        end;
        IA.all := IA.all + 1;             -- теперь это будет только ВАША ошибка!!!
    end Legal_But_Stupid;
    

Следует заметить, что применение атрибута 'Unchecked_Access - не рекомендуется. Это подразумевает, что при его использовании вы должны быть полностью уверены в своих действиях.

Еще одним способом, который позволяет обходить эти ограничения, является описание обобщенных ссылочных типов внутри настраиваемых модулей. Идея такого подхода основана на том, что область видимости для типа который описан внутри настраиваемого модуля будет ограничена областью видимости места конкретизации этого настраиваемого модуля.

12.3 Ссылочные типы для подпрограмм

Также как и обобщенные ссылочные типы, ссылочные типы для подпрограмм не доступны в стандарте Ada83. Они являются нововведением, которое определил выход стандарта Ada95.

Ссылочные типы для подпрограмм позволяют использовать подпрограммы как параметры передаваемые другим подпрограммам (например, так как это делается в математических библиотеках Фортрана), а также осуществлять позднее связывание.

При описании ссылочных типов для подпрограмм следует придерживаться следующих соглашений:

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

    
    
    type  Access_Function is access   function(Item: in     Float) return Float;
    
    type  Vector  is array (Integer range < >) of Float;
    
    procedure For_Each( F : in     Access_Function;
                        To: in out Vector           ) is
    begin
        for I in To'Range  loop
            To(I) := F( To(I) );
        end loop;
    end For_Each;
    

Здесь, процедура For_Each принимает в качестве параметра F значение ссылочного типа Access_Function указывющее на функцию, которую необходимо вызывать при обработке каждого элемента массива типа Vector, передаваемого ей как параметр To.

Примечательно, что при вызове функции F расшифровка ссылки производится автоматически. Следует также заметить, что вместо "F( To(I) )" можно было бы написать "F.all( To(I) )", что в подобных случаях - не обязательно. Использование .all требуется когда вызываемая подпрограмма (процедура или функция) не имеет параметров.

Описание переменной массива чисел, передаваемой как параметр To для процедуры For_Each, и описание функции, ссылка на которую могла бы быть использовна как параметр F процедуры For_Each, могут иметь следующий вид:

    
    
    Data_Vector : Vector (1..3) := (1.0, 2.0, 3.0);
    
    function Square(Val: in     Float) return Float is
    begin
        return Val * Val;
    end Square;
    

Таким образом, вызов процедуры For_Each (с учетом приведенных ранее описаний) будет иметь вид:

    
    
    For_Each( F  => Square'Access,
              To => Data_Vector   );
    

Примечательно, что для получения ссылочного значения которое указывает на функцию Square используется атрибут 'Access.

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

    
    
    type Action_Operation is access procedure;
    
    procedure Add;
    procedure List;
    procedure Delete;
    
    Action : constant array (1..3) of Action_Operation := (
                                  Add'Access,
                                  List'Access,
                                  Delete'Access
    );
    
    
    
    type Math_Function is access function (I : in     Float) return Float;
    
    function Sin (F : in     Float) return Float;
    function Cos (F : in     Float) return Float;
    function Tan (F : in     Float) return Float;
    
    Math_Operation : constant array (1..3) of Math_Function := (
                                  Sin'Access,
                                  Cos'Access,
                                  Tan'Access
    );
    

Здесь формируются две таблицы вызовов подпрограмм: первая таблица вызовов представлена массивом Action, который содержит значения ссылочного типа Action_Operation, а вторая таблица вызовов представлена массивом Math_Operation, который содержит значения ссылочного типа Math_Function (заметим, что таблицами вызовов, как правило, являются массивы).

Примером вызова I-той подпрограммы в таблице (где I - это индекс в таблице) может служить следующее:

    
    
    F:  Float;
      . . .
      
    Action(I).all;              -- вызов I-той процедуры из таблицы
                                --   Action
    F := Math_Operation(I)(F);  -- вызов I-той функции из таблицы
                                --   Math_Operation с параметром F
    

Напомним, что для ссылочных значений, которые указывают на подпрограммы без параметров, при вызове подпрограммы требуется использование .all.

12.3.1 Правила области видимости ссылочных типов для подпрограмм

Ссылочные типы для подпрограмм используют те же самые правила области видимости, что и обобщенные ссылочные типы. Таким образом, ссылочные типы для подпрограмм, которые описаны на уровне библиотеки, могут быть использованы только для ссылки на библиотечные подпрограммы. Дополнительным ограничением является то, что со ссылочными типами для подпрограмм нельзя использовать атрибут 'Unchecked_Access.

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

12.4 Низкоуровневая средства работы со ссылочными типами
       и физическими адресами памяти

Для организации работы с динамическими данными и динамической памятью в большинстве приложений достаточно тех механизмов, которые предоставляют ссылочные типы Ады. Однако, при решении специфических системных задач, может возникнуть необходимость непосредственного взаимодействия с физическими адресами памяти используемой аппаратной платформы.

Для таких случаев Ада предусматривает стандартный набор низкоуровневых средств. Так, в стандартном пакете System представлены:

При решении таких задач, важно учитывать тот факт, что реализация внутреннего представления значений ссылочных типов не обязана соответствовать представлению физических адресов системы. Стандарт Ады этого не требует. Для выполнения преобразований значений ссылочных типов в значение физического адреса и наоборот, следует использовать настраиваемый пакет System.Address_To_Access_Conversions, спецификация которого имеет следующий вид:

    
    
    generic
       type Object (<>) is limited private;
    
    package System.Address_To_Access_Conversions is
    
       type Object_Pointer is access all Object;
       for Object_Pointer'Size use Standard'Address_Size;
    
       function To_Pointer (Value : Address)        return Object_Pointer;
       function To_Address (Value : Object_Pointer) return Address;
    
    end System.Address_To_Access_Conversions;
    

И в заключение, в стандартном пакете System.Storage_Elements предоставлены операции адресной арифметики и некоторые дополнительные описания (за более подробными сведениями следует обратиться к файлу спецификации этого пакета).


Copyright (C) А.Гавва V-0.4w май 2004