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

         

Создание задачи


Напомним, что тип задачи может рассматриваться как шаблон для создания реальных объектов-задач.

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

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

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

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

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

Выполнение объекта-задачи имеет три основные фазы:

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

Нормальное выполнение - выполнение инструкций, видимых внутри тела задачи.

Завершение - выполнение любого кода завершения (finalization), ассоциированного с любым объектом в описательной части задачи.

Вновь созданная задача находится в неактивированом (unactivated) состоянии.

Затем, система времени выполнения осуществляет ассоциирование с этой задачей потока управления (thread of control).

Если элаборация задачи терпит неудачу, то задача сразу переходит в прекращенное (terminated) состояние.

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

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




Когда задача выполняет альтернативу завершения (terminate alternative) или нормальным образом завершает свое выполнение, она переходит в прекращенное (terminated) состояние.

Задача индицирует свою готовность к началу завершения выполнением инструкции end.

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

Задача, которая окончила свою работу называется завершенной (completed) или прекращенной (terminated), в зависимости от наличия активных задач которые от нее зависят.



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

Говорят, что задача зависит от своего владельца.

Задачу, которая осуществляет выполнение владельца, называют ведущей или родительской задачей (parent task).

Таким образом, для каждой задачи существует родительская задача от которой она также зависит.

Приняты следующие правила:

Если задача была описана как объект, то ее родительская задача - это та задача, которая содержит описание объекта задачи.

Если задача была описана как часть аллокатора new, то ее родительская задача - это та задача, которая содержит соответствующее описание ссылочного типа.

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

Как только все дочерние задачи завершают свою активацию, родительская задача и все ее дочерние задачи продолжают свое выполнение независимо.

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

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

(environment task), которая ответственна за элаборацию всей программы.

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

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

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


Спецификация пакета


Как уже говорилось, спецификация пакета Ады определяет интерфейс доступа к вычислительным ресурсам (сервисам) пакета.

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

Простой пример спецификации пакета может иметь следующий вид:

package Odd_Demo is

type A_String is array (Positive range <>) of Character;

Pi : constant Float := 3.14;

X : Integer;

type A_Record is record

Left : Boolean; Right : Boolean; end record;

-- примечательно, что дальше, для двух подпрограмм представлены только -- их спецификации, тела этих подпрограмм будут находиться в теле пакета

procedure Insert(Item : in Integer; Success : out Boolean); function Is_Present(Item : in Integer) return Boolean;

end Odd_Demo;

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

Полная точечная нотация, в общем случае, имеет следующий вид:

имя_пакета.имя_используемого_ресурса

где имя_используемого_ресурса - это имя типа, подпрограммы, переменной и т.д.

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

with Odd_Demo;

procedure Odder_Demo is

My_Name : Odd_Demo.A_String; Radius : Float; Success : Boolean;

begin

Radius := 3.0 * Odd_Demo.Pi; Odd_Demo.Insert(4, Success); if Odd_Demo.Is_Present(34) then ... . . . end Odder_Demo;

Не трудно заметить, что доступ к ресурсам пакета, текстуально подобен доступу к полям записи.

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

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




with Odd_Demo; use Odd_Demo;

procedure Odder_Demo is

My_Name : A_String; Radius : Float; Success : Boolean;

begin

Radius := 3.0 * Pi; Insert(4, Success); if Is_Present(34) then ... . . . end Odder_Demo;

Если два пакета, указаны в инструкциях with и use в одном компилируемом модуле (например, в подпрограмме или другом пакете), то возможно возникновение коллизии имен используемых ресурсов, которые предоставляются двумя разными пакетами.

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

------------------------------- package No1 is

A, B, C : Integer; end No1;

------------------------------- package No2 is

C, D, E : Integer; end No2;

------------------------------- with No1; use No1; with No2; use No2;

procedure Clash_Demo is

begin

A := 1; B := 2;

C := 3; -- двусмысленность, мы ссылаемся -- на No1.c или на No2.c?

No1.C := 3; -- избавление от двусмысленности путем возврата No2.C := 3; -- к полной точечной нотации end Clash_Demo;

Может возникнуть другая проблема - когда локально определенный ресурс "затеняет" ресурс пакета указанного в инструкции use.

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

package No1 is

A : Integer; end No1;

with No1; use No1; procedure P is

A : Integer; begin

A := 4; -- это - двусмысленно P.A := 4; -- удаление двусмысленности путем указания -- имени процедуры в точечной нотации No1.A := 5; -- точечная нотация для пакета end P;


Спецификация внутреннего представления данных


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

Предположим, что у нас есть перечислимый тип, который описывает страны:

type Country is (USA, Russia, France, UK, Australia);

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

type Country is (USA, Russia, France, UK, Australia); for Country use (USA => 1, Russia => 7, France => 33, UK => 44, Australia => 61);

Таким образом, внутреннее значение используемое, например, для представления идентификатора France будет 33.

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

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

Country'Succ(USA) возвратит: Russia
Country'Pred(Australia)   возвратит: UK
Country'Pos(Russia)   возвратит: 1
Country'Val(2)   возвратит: France

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

Следует заметить, что использование модуля настраиваемой функции Ada.Unchecked_Convertion

обладает одним ограничением: объект-источник и объект-приемник должны иметь одинаковый битовый размер.

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

Например, можно указать, что размер внутреннего представления типа Country




должен быть равен размеру типа Integer.

Это можно выполнить следующим образом:

type Country is (USA, Russia, France, UK, Australia); for Country'Size use Integer'Size; for Country use (USA => 1, Russia => 7, France => 33, UK => 44, Australia => 61);

Напомним, что атрибут T'Size

возвращает размер экземпляра объекта типа T в битах.

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

with Ada.Unchecked_Conversion; with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

type Country is (USA, Russia, France, UK, Australia); for Country'Size use Integer'Size; for Country use (USA => 1, Russia => 7, France => 33, UK => 44, Australia => 61);

function International_Dialing_Code is

new Ada.Unchecked_Conversion (Country, Integer);

begin

Put ("International Dialing Code for France is "); Put ( Integer'Image(International_Dialing_Code (France)) ); New_Line; end Main;


Спецификатор "use type"


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

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

Эта модифицированная форма спецификатора использования имеет следующий вид:

use type имя_типа

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

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



Сравнение массивов


Для сравнения одномерных массивов могут быть использованы следующие знаки операций "<", "<=", ">" и ">=". Они наиболее полезны при сравнении массивов символов (строк).

"hello" < "world" -- возвращает результат "истина" (True)

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



Средства сокрытия деталей реализации внутреннего представления данных


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

Однако, в примерах, показанных ранее, от пользователей пакетов были скрыты только детали реализации подпрограмм, а все типы данных были открыто описаны в спецификации пакета.

Следовательно, все детали реализации представления внутренних структур данных "видимы" пользователям.

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

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

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

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

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

К сожалению, в реальной жизни все сложнее.

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

Ада позволяет "закрыть" детали внутреннего представления данных и, таким образом, избавиться от проблемы массовых переделок. Такие возможности обеспечивает использование приватных типов (private types) и лимитированных приватных типов (limited private types).



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


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

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



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


Также как и обобщенные ссылочные типы, ссылочные типы для подпрограмм не доступны в стандарте 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.


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


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

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

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

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

Такие данные, как правило, называют динамическими данными или данными с динамической структурой.

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

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

Такой механизм доступа к данным называют механизмом косвенного доступа.

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

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

Известным обладателем подобной проблемы является семейство языков C/C++.

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

При этом ссылочные типы Ады обладают следующими характерными особенностями:

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




Значения ссылочного типа не рассматриваются как целочисленные зачения, а значит, они не поддерживают адресную арифметику.

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

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

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

Как правило, это делается с целью обеспечить более традиционное изложение материала.

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

ссылочные типы для динамической памяти обобщенные ссылочные типы ссылочные типы для подпрограмм

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


Стандартные низкоуровневые средства, пакет AdaTags


Стандартным низкоуровневым средством работы с тэговыми типами является пакет Ada.Tags.

Спецификация этого пакета, согласно стандарта, имеет следующий вид:

package Ada.Tags is

type Tag is private;

function Expanded_Name(T : Tag) return String; function External_Tag(T : Tag) return String; function Internal_Tag(External : String) return Tag;

Tag_Error : exception;

private

. . . -- стандартом не определено

end Ada.Tags;

Функция Expanded_Name возвращает полное расширенное имя типа, индефицируемого значением тэга, в виде строки (в верхнем регистре).

Результат будет зависеть от реализации компилятора, если тип описан внутри неименованного блока инструкций.

Функция External_Tag возвращает строку, которая может быть использована для внешнего представления указанного тэга.

Вызов External_Tag(S'Tag) эквивалентен обращению к атрибуту S'External_Tag.

Функция Internal_Tag возвращает тэг который соответствует указанному внешнему представлению тэга, или возбуждает исключение Tag_Error

если ни для одного из типов, в пределах раздела программы, указанная строка не является внешним представлением тэга.

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



Стандартные операции для массивов


Существует несколько операций, которые могут выполняться не только над отдельными элементами массива, но и над целым массивом.



Строки языка C


Описание массива символов языка C имеет следующий вид:

type Char_Array is array (Size_T range <>) of aliased Char;

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

Нулевой символ используется как индикатор конца строки.

Таким образом, описание строки Name, содержащей текст "Vasya", которая может быть передана как параметр для функции языка C

может иметь следующий вид:

Name : constant Char_Array := "Vasya" & nul;

Примечательно, что nul используется для представления нулевого символа (индикатор конца строки) и не является зарезервированным словом Ады null.

Для выполнения символьной конверсии можно использовать функции:

function To_C (Item: in Character) return Char; function To_Ada (Item: in Char) return Character;

Для выполнения строковой конверсии можно использовать функции:

function To_C (Item : in String; Append_Nul : in Boolean := True) return Char_Array; function To_Ada (Item : in Char_Array; Trim_Nul : in Boolean := True) return String;

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

- обратно.



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


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

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

Ада позволяет решить подобную проблему осуществляя неполное описание типа.

При неполном описании просто указывается имя типа, который еще не описан.

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

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.



Связь с другими языками в Ada


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

Предположим, что при работе в системе Unix, необходимо использовать команду kill.

Для осуществления этого, необходимо выполнить следующее:

function kill( pid : in Integer; sig : in Integer) return Integer;

pragma Interface(C, kill);

В данном случае, первый параметр директивы компилятора Interface - это язык вызываемой подпрограммы, а второй - имя подпрограммы под которым она (подпрограмма) известна в программе на Аде.

Пример пакета который импортирует функции написанные на Фортране может иметь следующий вид.

package MATHS is

function sqrt(X : Float) return Float; function exp (X : Float) return Float; private

pragma Interface(Fortran, sqrt); pragma Interface(Fortran, exp); end MATHS;

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




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

Согласно стандарта Ada95, для организации взаимодействия с программным обеспечением, написанным на других языках программирования, можно использовать стандартные директивы компилятора Import, Export, Convention и Linker_Options.

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

и его дочерние модули.



Текстовый ввод/вывод


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

Теперь, необходимо рассмотреть эти механизмы более детально.

Как известно, текстовыми файлами являются файлы которые содержат символы. Как мы уже знаем, согласно стандарта Ada95, Ада обеспечивает поддержку двух символьных типов: Character и Wide_Character.

Поэтому, средства поддержки текстового ввода/вывода Ады подразделяются на средства поддержки ввода/вывода для символов типа Character и средства поддержки ввода/вывода для символов типа Wide_Character.

Следует заметить, что стандартные пакеты поддержки текстового ввода/вывода для символов типа Character

содержат в своем названии строку Text_IO, а стандартные пакеты поддержки текстового ввода/вывода для символов типа Wide_Character

содержат в своем названии строку Wide_Text_IO.

Поскольку логика обработки текстового ввода/вывода для символов типа Character

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



Тело пакета


Тело пакета содержит все детали реализации сервисов, указаных в спецификации пакета.

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

package body Odd_Demo is

type List is array (1..10) of Integer; Storage_List : List; Upto : Integer;

procedure Insert(Item : in Integer; Success : out Boolean) is

begin

. . . end Insert;

function Is_Present(Item : in Integer) return Boolean is

begin

. . . end Is_Present;

begin -- действия по инициализации пакета -- это выполняется до запуска основной программы! for I in Storage_List'Range loop

Storage_List(I) := 0; end loop; Upto := 0; end Odd_Demo;

Все ресурсы, указанные в спецификации пакета, будут непосредственно доступны в теле пакета без использования дополнительных инструкций спецификации контекста with и/или use.

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

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

Любая попытка обращения к ним из другого модуля будет приводить к ошибке компиляции.

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

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

Раздел "begin ... end", в конце тела пакета, содержит перечень инструкций инициализации для этого пакета.

Инициализация пакета выполняется до запуска на выполнение главной подпрограммы.

Это справедливо для всех пакетов.

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



Тип Root_Integer


Модель целочисленной арифметики Ады базируется на понятии неявного типа Root_Integer. Этот тип используется как базовый тип для всех целочисленных типов Ады. Другими словами - все целочисленные типы являются производными от типа Root_Integer

(см. Производные типы). Диапазон значений типа Root_Integer определяется как System.Min_Int..System.Max_Int. Все знаки арифметических операций описаны так, чтобы они могли выполняться над этим типом.

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

type X is new Integer range 0 .. 100; type Y is range 0 .. 100;

Здесь, тип X описывается как производный от типа Integer

с допустимым диапазоном значений от 0 до 100. Исходя из этого, для типа X базовым типом будет тип Integer.

Тип Y описывается как тип с допустимым диапазоном значений от 0 до 100, и при его описании не указан тип-предок. В таком случае, он будет производным от типа Root_Integer, но его базовый диапазон не обязательно должен быть таким же как у Root_Integer. В результате, некоторые системы могут размещать экземпляры объектов такого типа и его базового типа в одном байте. Другими словами, определение размера распределяемого места под объекты такого типа возлагается на компилятор.



Тип Universal_Integer


Для предотвращения необходимости явного преобразования типов при описании целочисленных констант, Ада предлагает понятие универсального целого типа - Universal_Integer. Все целочисленные литералы принадлежат типу Universal_Integer. Многие атрибуты языка (обсуждаемые позже) также возвращают значения универсального целого типа. Установлено, что тип Universal_Integer совместим с любым другим целочисленным типом, поэтому, в арифметических выражениях, компилятор будет автоматически преобразовывать значения универсального целого типа в значения соответствующего целочисленного типа.



Типы и объекты задач


Конструкция задачи обладает свойствами, которые характерны для пакетов, процедур и структур данных:

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

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

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

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

Подобно структуре данных, задача имеет тип и может существовать как переменная этого типа. Кроме того, подобно записи, задача может иметь дискриминанты.

Спецификация задачи, начинающаяся зарезервированными словами task type, определяет тип задачи (или задачный тип). Значение объекта (переменная) типа задачи представляет собой задачу.

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

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

with Ada.Text_IO;

procedure Multitasking_Demo is

-- спецификация анонимной задачи task Anonimous_Task;

-- тело анонимной задачи task body Anonimous_Task is

begin -- для Anonimous_Task

for Count in 1..5 loop

Ada.Text_IO.Put_Line("Hello from Anonimous_Task"); end loop;

end Anonimous_Task;

-- спецификация типа задачи task type Simple_Task (Message: Character);

-- тип задачи имеет тело task body Simple_Task is

begin -- для Simple_Task

for Count in 1..5 loop

Ada.Text_IO.Put_Line("Hello from Simple_Task " & Message); end loop;

end Simple_Task;

-- переменная задачного типа Simple_Task_Variable: Simple_Task(Message => 'A');

begin -- для Multitasking_Demo

-- в отличие от процедур, задачи не вызываются, -- а активируются автоматически

-- выполнение обоих задач начинается как только -- управление достигнет этой точки, то есть, сразу -- после "begin", но перед выполнением первой инструкции -- головной процедуры Multitasking_Demo

null;

end Multitasking_Demo;

<


В данном примере описана одиночная задача анонимного типа Anonimous_Task, тип задачи Simple_Task и переменная задачи Simple_Task_Variable, имеющая тип Simple_Task.

Примечательно, что описание типа задачи Simple_Task содержит дискриминант, значение которого используется как параметр задачи и указывается при описании переменной задачи Simple_Task_Variable.

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

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

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

with Ada.Text_IO;

procedure Multitasking_Demo_2 is

-- спецификация типа задачи task type Simple_Task (Message: Character; How_Many: Positive);

-- тело типа задачи task body Simple_Task is

begin -- для Simple_Task

for Count in 1..How_Many loop

Ada.Text_IO.Put_Line("Hello from Simple_Task " & Message); delay 0.1; end loop;

end Simple_Task;

-- переменные задачного типа Simple_Task_A: Simple_Task(Message => 'A', How_Many => 5); Simple_Task_B: Simple_Task(Message => 'B', How_Many => 10);

begin -- для Multitasking_Demo_2

null;

end Multitasking_Demo_2;

В этом примере, тип задачи Simple_Task содержит два дискриминанта: дискриминант Message типа Character используется при выдаче приветственного сообщения, как и ранее, а дискриминант How_Many используется для указания количества выдаваемых сообщений.

Таким образом, переменная задачи Simple_Task_A

выдаст пять приветствий, а переменная Simple_Task_B - десять.


Типы и подтипы


Как уже говорилось, концепция типа является в Аде основопологающим фактором создания надежного программного обеспечения. Предположим, что у нас есть два целочисленных типа, которые как-то характеризуют "звоночки" и "свисточки", и мы никак не хотим смешивать эти понятия. Нам необходимо, чтобы компилятор имел возможность предупредить нас: "Ба, да вы пытаетесь смешать выши "звоночки" и "свисточки"! Что Вы в действительности подразумеваете?". Это выглядит излишними осложнениями для людей, которые используют другие языки программирования со слабой типизацией данных. Размышления над тем какие данные необходимо представить, описание различных типов данных и правил конвертирования типов требуют некоторых усилий. Однако, такие усилия оправдываются тем, что как только это сделано, компилятор сможет помочь отыскать разные глупые ошибки.

Бывают случаи, когда нам необходимо указать, что какие-то переменные могут хранить только какое-то ограниченное число значений предоставляемых определенным типом данных. При этом, мы не хотим описывать самостоятельный новый тип данных, поскольку это потребует явного указания правила преобразования типа при взаимодействии со значениями, которые имеют оригинальный тип данных, то есть нам необходима возможность смешивать различные типы, которые, по своей сути, являются одинаковыми сущностями, но при этом имеют некоторые различия в своих характеристиках. К тому же, мы хотим сохранить преимущества проверки корректности наших действий, которые предоставляет компилятор. В таких случаях, Ада позволяет нам описывать подтипы (subtype) существующих типов данных (это соответствует типам определяемым пользователем в Паскале).

Общий синтаксис объявления подтипа имеет вид:

subtype Name_1 is Base_Type; -- в данном случае, Name_1 является -- синонимом типа Base_Type

subtype Name_2 is Base_Type range Lowerbound..Upperbound;

Примеры объявления подтипов приводятся ниже:

type Processors is (M68000, i8086, i80386, M68030, Pentium, PowerPC); subtype Old_Processors is Processors range M68000..i8086; subtype New_Processors is Processors range Pentium..PowerPC;

subtype Data is Integer; subtype Age is Data range 0..140; subtype Temperatures is Float range -50.0..200.0; subtype Upper_Chars is Character range 'A'..'Z';

<


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

My_Age : Age; Height : Integer;

Height := My_Age; -- глупо, но никогда не вызывает проблем

My_Age := Height; -- может вызвать проблемы, когда значение типа Height

-- будет за пределами диапазона значений My_Age (0..140), -- но при этом остается совметимым

Чтобы избежать генерацию исключительной ситуации, можно использовать проверки принадлежности диапазону ("in" и/или "not in"). Например:

I : Integer; N : Natural;

. . .

if I in Natural then

N := I else

Put_Line ("I can't be assigned to N!"); end if;

. . .

Реально, все типы Ады являются подтипами анонимных типов, рассматриваемых как их базовые типы. Поскольку базовые типы анонимны, то на них нельзя ссылаться по имени. При этом, для получения базового типа можно использовать атрибут 'Base. Например, Integer'Base - это базовый тип для Integer. Базовые типы могут иметь или могут не иметь диапазон значений больший чем их подтипы. Это имеет значение только в выражениях вида "A * B / C" которые, при вычислении промежуточных значений, используют базовый тип. То есть, результат "A * B" может выходить за пределы значений типа не приводя к генерации исключительной ситуации если общий результат вычисленного значения всего выражения будет находиться в допустимом диапазоне значений для данного типа.

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


Типы неограниченных массивов (unconstrained array), предопределенный тип String


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

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

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

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

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

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

type Numbers_Array is array (Positive range <>) of Integer;

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

Numbers : Numbers_Array (1..5) := (1, 2, 3, 4, 5);

Здесь, при описании переменной Numbers предусматривается ограничение (constraint) размеров массива - указывается диапазон значений индекса - (1..5).

Пакет Standard предоставляет предопределенный тип String, который описывается как неограниченный массив символов:

type String is array (Positive range <>) of Character;

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




Также как и в предыдущем примере описания переменной Numbers, для создания фактического массива типа String, мы должны предусмотреть ограничение диапазона возможных значений индекса:

My_Name : String (1..20);

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

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

My_Name := "Alex ";

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

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

Some_Name : String := "Vasya Pupkin"; Some_Saying : constant String := "Beer without vodka is money to wind!";

Для обработки каждого элемента переменной, которая порождается при использовании типа неограниченного массива, требуется использование таких атрибутов типа массив, как A'Range, A'First и т.д., поскольку не известно какие индексные значения будет иметь обрабатываемый массив.

My_Name : String (1..20); My_Surname : String (21..50);

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


Типы Universal_Float и Root_Real


Подобно тому как все целочисленные литералы принадлежат универсальному классу Universal_Integer, все вещественные численные литералы принадлежат универсальному классу Universal_Float. Этот универсальный тип совместим с любым вещественным типом с плавающей точкой и любым вещественным типом с фиксированной точкой. Такой подход избавляет от необходимости выполнения различных преобразований типов при описании вещественных констант и переменных.

Модель вещественной арифметики Ады основывается на анонимном типе Root_Real. Этот анонимный тип используется как базовый тип для всех вещественных типов. Тип Root_Real имеет точность, которая определяется значением константы Max_Base_Digits пакета System (System.Max_Base_Digits). Такой подход использован для облегчения переносимости программ.



Тэговые типы (tagged types)


Тэговые типы являются нововведением стандарта Ada95.

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

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

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

В традиционном понимании, слово "класс" трактуется как спецификация типа данных и множество методов (операций) этого типа данных.

В отличие от этого, Ада трактует понятие "класс" как набор типов которые объединены иерархией наследования.



Указание значения параметра по-умолчанию


Для любых "in"-параметров ("in" или "in out"), в спецификации подпрограммы можно указать значение параметра по-умолчанию.

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

with Ada.Text_IO; use Ada.Text_IO;

procedure Print_Lines(No_Of_Lines: Integer := 1) is

begin

for Count in 1 .. No_Of_Lines loop

New_Line; end loop; end Print_Lines;

Такое описание устанавливает значение параметра No_Of_Lines

для случаев когда процедура Print_Lines

вызывается без указания значения этого параметра (позиционного или именованного).

Таким образом, вызов этой процедуры может иметь вид:

Print_Lines; -- это печатает одну строку Print_Lines(6); -- переопределяет значение параметра -- установленное по-умолчанию

Подобно этому, если процедура Write_Lines была описана как:

with Ada.Text_IO; use Ada.Text_IO;

procedure Write_Lines(Letter : in Char := '*'; No_Of_Lines : in Integer := 1) is

begin

for I in 1 .. No_Of_Lines loop

for I in 1 .. 80 loop

Put(Letter); end loop; New_Line; end loop; end Write_Lines;

то она может быть вызвана следующими способами:

Write_Lines; -- для параметров Letter и No_Of_Lines

-- используются значения устанавливаемые -- по-умолчанию Write_Lines('-'); -- значение по-умолчанию - для No_Of_Lines

Write_Lines(no_of_lines => 5); -- значение по-умолчанию - для Letter

Write_Lines('-', 5) -- оба параметра определены



Уменьшение длин имен


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

with Ada.Text_IO; with Ada.Integer_Text_IO;

procedure Gun_Aydin is

package TIO renames Ada.Text_IO; package IIO renames Ada.Integer_Text_IO;

. . .

В этом случае, для внутреннего использования, длинное имя Ada.Text_IO переименовано в короткое имя TIO, а длинное имя Ada.Integer_Text_IO переименовано в короткое имя IIO.



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


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

Предположим, что у нас есть пакет 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 -- ------------------------------------------

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


Управляющие структуры


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

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

Как и язык в целом, управляющие структуры языка Ада, были разработаны так, чтобы обеспечить максимальную читабельность. Все управляющие структуры языка Ада можно разделить на простые и составные инструкции. Все инструкции языка Ада должны завершаться символом точки с запятой. Все составные инструкции завершаются конструкцией вида "end что-нибудь ;".



Условные инструкции if


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

Каждая инструкция if заканчивается конструкцией "end if".

if <логическое_выражение> then

-- последовательность инструкций

end if;

if <логическое_выражение> then

-- последовательность инструкций 1

else

-- другая последовательность инструкций 2

end if;

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

будет выполняться "последовательность инструкций 1", в противном случае - "последовательность инструкций 2".

Для сокращения инструкций вида "else if ... ", и в целях улучшения читабельности, введена конструкция elsif, которая может быть использована столько раз, сколько это будет необходимо.

if <логическое_выражение> then

-- последовательность инструкций 1

elsif <логическое_выражение> then

-- последовательность инструкций 2

elsif <логическое_выражение> then

-- последовательность инструкций 3

else

-- последовательность инструкций

end if;

В этой форме инструкции if, заключительная конструкция else - опциональна.

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



Вариантные записи


Использование дискриминантов позволяет конструировать вариантные записи (или, называемые иначе, записи с вариантами). В этом случае, значение дискриминанта определяет наличие или отсутствие некоторых полей записи.

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

type Vehicle is (Bicycle, Car, Truck, Scooter);

type Transport (Vehicle_Type : Vehicle) is record

Owner : String(1..10); -- владелец Description : String(1..10); -- описание case Vehicle_Type is

when Car => Petrol_Consumption : Float; -- расход бензина when Truck => Diesel_Consumption : Float; -- расход солярки Tare : Real; -- вес тары Net : Real; -- вес нетто when others => null; end case; end record;

В представленном описании типа записи Transport, поле Vehicle_Type (vehicle - транспортное средство) является дискриминантом.

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

My_Car : Transport (Car); My_Bicycle : Transport (Vehicle_Type => Bicycle); His_Car : Transport := (Car, "dale ", "escort ", 30.0);

Здесь, переменные My_Car и His_Car, у которых дискриминант имеет значение Car, содержат поля: Owner, Description и Petrol_Consumption. При этом, попытка обращения к таким полям этих переменных как Tare и/или Net будет не допустима. В результате, это приведет к генерации ошибки ограничения (constraint error), что может быть обнаружено и обработано при использовании механизма исключений (exceptions) язака Ада.

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

Также отметим, что в приведенных примерах тип записи, для простоты, имеет только один дискриминант. Реально, запись может содержать столько дискриминантов, сколько необходимо для решения конкретной задачи.

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

вариантная часть всегда записывается последней запись может иметь только одну вариантную часть, однако внутри вариантной части разрешается указывать другой раздел вариантов для каждого значения дискриминанта должен быть указан свой список компонентов альтернатива others допустима только для последнего варианта, она задает все оставшиеся значения дискриминанта (возможно, и ни одного), которые не были упомянуты в предыдущих вариантах если список компонентов варианта задан как null, то такой вариант не содержит никаких компонентов



Вещественные типы


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



Вещественные типы с десятичной фиксированной точкой


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

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

type Money is delta 0.01 digits 15; -- десятичная фиксированная точка, -- здесь величина, задаваемая в delta, -- должна быть степенью 10 subtype Salary is Money digits 10;



Вещественные типы с фиксированной точкой, тип Duration


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

В пакете Standard предоставлен предопределенный вещественный тип с фиксированной точкой Duration, который используется для представления времени и обеспечивает точность измерения времени в 50 микросекунд:

type Duration is delta 0.000000001 range -((2 ** 63 - 1) * 0.000000001) .. +((2 ** 63 - 1) * 0.000000001);

Ниже следуют примеры описаний вещественных типов с фиксированной точкой.

type Volt is delta 0.125 range 0.0 .. 255.0;

type Fraction is delta System.Fine_Delta range -1.0..1.0; -- Ada95 -- Fraction'Last = 1.0 - System.Fine_Delta

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



Вещественные типы с плавающей точкой, тип Float


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

Пакет Standard предоставляет предопределенный вещественный тип с плавающей точкой Float, который обеспечивает точность в шесть десятичных цифр после запятой:

type Float is digits 6 range -16#0.FFFF_FF#E+32 .. 16#0.FFFF_FF#E+32; -- -3.40282E+38 .. 3.40282E+38

В пакете Standard компилятора GNAT, для 32-битных систем Linux и Windows, дополнительно представлены еще несколько вещественных типов с плавающей точкой (фактические значения констант для различных платформ отличаются):

type Short_Float is digits 6 range -16#0.FFFF_FF#E+32 .. 16#0.FFFF_FF#E+32; -- -3.40282E+38 .. 3.40282E+38

type Long_Float is digits 15 range -16#0.FFFF_FFFF_FFFF_F8#E+256 .. 16#0.FFFF_FFFF_FFFF_F8#E+256; -- -1.79769313486232E+308 .. 1.79769313486232E+308

type Long_Long_Float is digits 18 range -16#0.FFFF_FFFF_FFFF_FFFF#E+4096 .. 16#0.FFFF_FFFF_FFFF_FFFF#E+4096; -- -1.18973149535723177E+4932 .. 1.18973149535723177E+4932

Ниже следуют примеры описаний вещественных величин с плавающей точкой.

X : Float; A, B, C : Float; Pi : constant Float := 3.14_2; Avogadro : constant := 6.027E23; -- тип Universal_Float

subtype Temperatures is Float range 0.0..100.0; type Result is new Float range 0.0..20_000.0;

type Velocity is new Float; type Height is new Float; -- нельзя случайно смешивать Velocity и Height

-- без явного преобразования типов.

type Time is digits 6 range 0.0..10_000.0; -- в этом диапазоне требуемая точность - шесть десятичных цифр -- после точки

type Degrees is digits 2 range -20.00..100.00; -- требуемая точность - две десятичных цифры после точки

Следующие знаки операций предопределены для каждого вещественного типа с плавающей точкой.

+, -, *, / ** возведение в степень (только целые значения степени) abs абсолютное значение



Вложенные структуры


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



Вложенные записи


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

type Point is record

X : Integer; Y : Integer; end record

type Rect is record

Left_Hight_Corner : Point; Right_Low_Corner : Point; end record

P : Point := (100, 100); R : Rect;

В этом случае, доступ к полям переменной R типа Rect

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

R.Left_Hight_Corner.X := 0; R.Left_Hight_Corner.Y := 0;

R.Right_Low_Corner := P;

Для указания всех значений можно использовать агрегаты.

R_1 : Rect := ( (0, 0), (100, 100) ); R_2 : Rect := ( Left_Hight_Corner => (Y => 0, X => 0), Right_Low_Corner => (100, 100) );

Как видно из приведенных примеров, здесь используются вложенные агрегаты.



Возбуждение исключений


Указание возбуждения исключения достаточно простое.

Для этого используется инструкция raise. Например:

raise Numeric_Error;

Сгенерированное таким образом исключение не будет ничем отличаться от "истинного" исключения Numeric_Error.



Введение в систему типов языка Ада


Данные - это то, что обрабатывает программа. Практически, любой современный язык программирования определяет свои соглашения и механизмы для разделения данных на разные типы.

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

Понятие типа данных Ады подразумевает, что:

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

Исходя из сказанного, в отличие от других языков программирования, Ада не допускает

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

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

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




Приводимая ниже диаграмма демонстрирует общую организацию системы типов Ады.
Все типы
| |- Простые типы
| | | |- Скалярные типы
| | | | | |- Вещественные (Real)
| | | | | | | |- Универсальный Вещественный (Universal_Real) -- все вещественные | | | | -- литералы | | | | | | | |- Корневой Вещественный (Root_Real) -- только Ada95 | | | | | | | |- с плавающей точкой (Floating Point)
| | | |- с фиксированной точкой (Fixed Point)
| | | | | | | |- с обычной фиксированной точкой
| | | | (Ordinary Fixed Point)
| | | | | | | |- с десятичной фиксированной точкой -- только Ada95 | | | (Decimal Fixed Point)
| | | | | |- Дискретные типы
| | | | | |- Целые типы
| | | | | | | |- Универсальный Целый (Universal_Integer) -- все | | | | -- целочисленные | | | | -- литералы | | | | | | | |- Корневой Целый (Root_Integer) -- только Ada95 | | | | | | | |- Знаковые Целые
| | | |- Модульные Целые (Modular Integer) -- только Ada95 | | | | | |- Перечислимые
| | | | | |- Символьные (Character, Wide_Character)
| | |- Логический (Boolean)
| | |- Определяемые пользователем
| | | |-- Ссылочные типы / указатели (Access)
| | | |- Ссылки на объекты
| |- Ссылки на подпрограммы -- только Ada95 | |- Составные типы
| |-- Массивы (Array)
| | | |- Строки (String)
| |- Другие, определяемые пользователем массивы
| |-- Записи (Record)
| |-- Тэговые Записи (Tagged Record) -- только Ada95 | |-- Задачи (Task)
| |-- Защищенные типы (Protected) -- только Ada95

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

Ввод/вывод двоичных данных


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

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

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

В качестве основы для организации ввода/вывода двоичных данных Ада предусматривает настраиваемые пакеты Ada.Sequential_IO и Ada.Direct_IO.

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



Выполнение подавления исключений


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

pragma Suppress (Idex_Check, on => table);

Подавление проверки исключений также может относиться к какому-то определенному типу:

type Employee_Id is new Integer; pragma Suppress (Range_Check, Employee_Id);

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

declare

pragma Suppress(Range_Check); subtype Small_Integer is Integer range 1..10;

A : Small_Integer; X : Integer := 50;

begin

A := X; end;

Этот код не будет генерировать ошибок ограничения (Constraint_Error).

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



Вызов переопределенной операции предка


Достаточно часто, реализация примитивной операции производного типа нуждается в вызове переопределенной примитивной операции предка.

Предположим, что для типов Root и Child_1

существуют описания операции Display имеющие следующий вид:

. . . procedure Display (Self: in Root); procedure Display (Self: in Child_1); . . .

Поскольку такие операции совмещены, то можно сказать, что реализация операции Display

типа Root (предка для типа Child_1) является "затененной", в виду переопределения реализации в производном типе Child_1.

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

procedure Display (Self: in Child_1) is

begin

Display ( Root(Self) ); -- вызов "затененной" операции предка . . . end Display;

Здесь, для вызова "затененной" реализации операции предка, используется явное преобразование представления параметра Self

к типу Root.

За счет этого, в подобных случаях, всегда осуществляется статическое связывание, а также отсутствует опасность получения бесконечного рекурсивного вызова.



Взаимодействие с программами написанными на C


Обсуждение взаимодействия с программами написанными на C

полагается на описания которые содержатся в стандартном пакете Interfaces.C.

В частности, этот пакет предусматривает средства преобразования объектов с типами языка C в объектыс типами Ады, и обратно.



Взаимодействие задач


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

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

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



Записи-константы


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

My_Bicycle : constant Bicycle := ( Hi_Tensile_Steel, Unknown, Front_Brake => Side_Pull, Rear_Brake => Side_Pull );

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



Записи (record)


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

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



Зарезервированные слова


Некоторые слова, такие как with, procedure, is, begin, end и т.д., являются частью самого языка программирования. Такие слова называют зарезервированными (или ключевыми) и они не могут быть использованы в программе в качестве имен идентификаторов. Полный список зарезервированных слов Ады приводится ниже:

abort else new return abs elsif not reverse * abstract end null accept entry select access exception separate * aliased exit of subtype all or and for others * tagged array function out task at terminate generic package then begin goto pragma type body private if procedure case in * protected * until constant is use raise declare range when delay limited record while delta loop record while digits renames do mod * requeue xor

Примечание:

Зарезервированные слова помеченные звездочкой введены стандартом Ada95.



Защищенные процедуры обработки прерываний


Ада предусматривает два стиля установки обработчиков прерываний: вложенный (nested) и не-вложенный (non-nested).

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

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

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

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

pragma Attach_Handler (Handler, Interrupt);

Здесь, Handler указывает имя защищенной процедуры без параметров, которая описана в защищенном описании, а Interrupt является выражением типа Interrupt_ID.

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

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

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

Выражение типа Interrupt_ID не обязано быть статическим.

В частности, его значение может зависить от дискриминанта защищенного типа. Например:

package Nested_Handler_Example is

protected type Device_Interface (Int_ID : Ada.Interrupts.Interrupt_ID) is

procedure Handler; pragma Attach_Handler (Handler, Int_ID);

end Nested_Handler_Example;

<


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

pragma Interrupt_Handler (Handler, Interrupt);

Здесь, также как и в первом случае, Handler указывает имя защищенной процедуры без параметров, которая описана в защищенном описании, а Interrupt является выражением типа Interrupt_ID.

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

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

Следовательно, они должны создаваться динамически, с помощью new.


Защищенные типы и объекты Защищенные подпрограммы


Защищенные модули (типы и объекты) Ады инкапсулируют данные и позволяют осуществлять доступ к ним с помощью защищенных подпрограмм или защищенных входов.

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

Защищенный модуль может быть описан как защищенный тип или как одиночный защищенный объект, что аналогично одиночной задаче.

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

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

Подобно задаче или пакету, защищенный модуль имеет спецификацию и тело.

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

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

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

В качестве простой иллюстрации рассмотрим пример следующго одиночного защищенного объекта:

-- спецификация защищенного объекта protected Variable is

function Read return Item; procedure Write(New_Value : Item);

private

Data : Item;

end Variable;

-- тело защищенного объекта protected body Variable is

function Read return Item is

begin

return Data; end;

procedure Write (New_Value : Item) is

begin

Data := New_Value; end;

end Variable;

Защищенный объект Variable предоставляет управляемый доступ к приватной переменной Data типа Item.

Функция Read позволяет читать, а процедура Write - обновлять текущее значение переменной Data.

Защищаемые данные и данные о состоянии объекта должны быть помещены в приватную часть спецификации.

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




Защищенные процедуры предусматривают взаимно исключающий доступ к данным защищенного модуля по чтению и/или записи.

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

Однако вызовы защищенных процедур и защищенных функций остаются взаимно исключающими.

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

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

Для обращения к защищенным подпрограммам используется традиционная точечная нотация:

X := Variable.Read; . . . Variable.Write (New_Value => Y);

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


Защищенные входы и барьеры


По аналогии со входами задач, защищенный модуль может иметь защищенные входы.

Действия, выполняемые при вызове защищенного входа, предусматриваются в его теле.

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

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

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

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

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

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

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

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

-- спецификация защищенного типа protected type Bounded_Buffer is

entry Put(X: in Item); entry Get(X: out Item);

private

A: Item_Array(1 .. Max); I, J: Integer range 1 .. Max := 1; Count: Integer range 0 .. Max := 0;

end Bounded_Buffer;

-- тело защищенного типа protected body Bounded_Buffer is

entry Put(X: in Item) when Count < Max is

begin

A(I) := X; I := I mod Max + 1; Count := Count + 1; end Put;

entry Get(X: out Item) when Count > 0 is

begin

X := A(J); J := J mod Max + 1; Count := Count - 1; end Get;

end Bounded_Buffer;

<


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

значений типа Item.

Доступ обеспечивается с помощью входов Put и Get.

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

declare

. . . My_Buffer: Bounded_Buffer; . . . begin

. . . My_Buffer.Put(X); . . . end;

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

. . . select

My_Buffer.Get(X); . . . -- список инструкций else

. . . -- список инструкций end select;

Поведение защищенного типа контролируется барьерами.

При вызове входа защищенного объекта выполняется проверка соответствующего барьера.

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

При описании переменной My_Buffer

буфер - пуст, и, таким образом, барьер для входа Put имеет значение True, а для входа Get - False.

Следовательно, будет выполняться только вызов Put, а вызов Get

будет отправлен в очередь.

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

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

Важно понять, что здесь нет задачи, которая непосредственно ассоциирована с самим буфером.

Вычисление барьеров эффективно выполняется системой времени выполнения.

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



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

Такие правила гарантируют эффективность реализации защищенного объекта.

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

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

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

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

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

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

Это имеет одно важное следствие: если состояние защищенного объекта изменяется, и существует задача, которая ожидает новое состояние защищенного объекта, то такая задача получает доступ к ресурсу.

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

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



Основопологающая концепция защищенных объектов подобна мониторам.

Они являются пассивными конструкциями с синхронизацией, предусматриваемой системой времени выполнения языка.

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

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

Защищенные модули позволяют осуществлять очень эффективную реализацию различных сигнальных объектов, семафоров и подобных им парадигм:

-- спецификация защищенного типа protected type Counting_Semaphore (Start_Count : Integer := 1) is

entry Secure; procedure Release; function Count return Integer;

private

Current_Count : Integer := Start_Count;

end;

-- тело защищенного типа protected body Counting_Semaphore is

entry Secure when Current_Count > 0 is

begin

Current_Count := Current_Count - 1; end;

procedure Release is

begin

Current_Count := Current_Count + 1; end;

function Count return Integer is

begin

return Current_Count; end;

end Counting_Semaphore;

Особенностью этого примера является то, что Start_Count является дискриминантом.

При вызове входа Secure опрашивается барьер этого входа.

Если результат опроса барьера есть False, то задача ставится в очередь, а ожидание обслуживания становится True.

Следует заметить, что этот пример демонстрирует реализацию общего семафора Дейкстры (Dijkstra), где вход Secure и процедура Release

соответствуют операциям P и V (Dutch Passeren и Vrijmaken), а функция Count возвращает текущее значение семафора.

Очевидно, что в очереди обращения к защищенному входу может одновременно находиться несколько задач.

Так же как и в случае с очередями к входам задач, очереди к входам защищенных объектов обслуживаются в порядке поступления вызовов (FIFO - First-In-First-Out).

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

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

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

Индекс семейства может использовать барьер, ассоциируемый с таким входом (обычно такой индекс используется в качестве индекса массива значений логического типа Standard.Boolean).


Завершение задачи


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

Каждая задача зависит от одного владельца, или более:

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

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

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

В первую очередь, зависимая задача дожидается очистки (finalization) владельца.

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

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

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



Значения полей записи по-умолчанию


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

type Bicycle is

record

Frame : Construction := CromeMolyebdenum; Maker : Manufacturer; Front_Brake : Brake_Type := Cantilever; Rear_Brake : Brake_Type := Cantilever; end record;