"Адское" программирование Ada-95 -Компилятор GNAT



"Адское" программирование Ada-95 -Компилятор GNAT

         

Приоритеты задач



Приоритеты задач

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

pragma Priority ( expression );

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

Значение результата выражения expression, используемого для непосредственного указания приоритета, должно принадлежать целочисленному типу Integer, причем при указании директивы Priority в описательной части тела подпрограммы выражение expression должно быть статическим,


а его значение должно принадлежать диапазону значений подтипа Priority, который описан в пакете System.Например:

task Some_Task is pragma Priority (5); . . . end Some_Task;

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

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

Средства динамического определения и изменения текущего приоритета задачи предоставляются предопределенным стандартным пакетом Ada.Dynamic_Priorities.Спецификация этого пакета проста и имеет следующий вид:

with System; with Ada.Task_Identification; -- спецификация этого пакета обсуждается в 1, -- при рассмотрении вопроса идентификации задач package Ada.Dynamic_Priorities is procedure Set_Priority (Priority : in System.Any_Priority; T : in Ada.Task_Identification.Task_ID := Ada.Task_Identification.Current_Task); function Get_Priority (T : Ada.Task_Identification.Task_ID := Ada.Task_Identification.Current_Task) return System.Any_Priority;end Ada.Dynamic_Priorities;

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



Присваивание



Присваивание

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

declare My_Name : String(0) := "Dale "; Your_Name : String(0) := "Russell "; Her_Name : String(20) := "Liz "; His_Name : String() := "Tim "; begin Your_Name := My_Name; -- это корректно, поскольку в обоих случаях Your_Name := Her_Name; -- оба массива имеют одинаковое количество -- элементов His_Name := Your_Name; -- это приведет к возбуждению исключения: -- хотя обе переменные одного и того же типа, -- но они имеют различную длину (число элементов) end;



Приватные дочерние модули (private child units)



Приватные дочерние модули (private child units)

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

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

private package Stacks.Statistics is procedure Increment_Push_Count;end Stacks.Statistics;

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

Приватные типы (private types)



Приватные типы (private types)

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

- изъятие средств (Withdraw)
- размещение средств (Deposit)
- создание счета (Create)

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

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

package Accounts is type Account is private; -- описание будет представлено позже procedure Withdraw(An_Account : in out Account; Amount : in Money); procedure Deposit( An_Account : in out Account; Amount : in Money); function Create( Initial_Balance : Money) return Account; function Balance( An_Account : in Account) return Integer;private -- эта часть спецификации пакета -- содержит полные описания type Account is record Account_No : Positive; Balance : Integer; end record; end Accounts;

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

- присваивание
- проверка на равенство (не равенство)
- проверки принадлежности ("in", "not in")

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

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

В данном примере необходимо обратить внимание на то, что спецификация пакета разделена на две части. Все что находится до зарезервированного слова private - это общедоступная часть описаний, которая будет "видна" всем пользователям пакета. Все что находится после зарезервированного слова private - это приватная часть описаний, которая будет "видна" только внутри пакета (и в его дочерних модулях; см. "Дочерние модули").

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

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

with Accounts; use Accounts;procedure Demo_Accounts is Home_Account : Account; Mortgage : Account; This_Account : Account;begin Mortgage := Accounts.Create(Initial_Balance => 500); Withdraw(Home_Account, 50); . . . This_Account := Mortgage; -- присваивание приватного типа - разрешено -- сравнение приватных типов if This_Account = Home_Account then . . .end Demo_Accounts;



Привязка объекта к фиксированному адресу памяти



Привязка объекта к фиксированному адресу памяти

В некоторых случаях может потребоваться выполнение чтения или записи по фиксированному абсолютному адресу памяти.Простым примером подобной ситуации может быть то, что операционная система MS-DOS хранит значение времени в фиксированных адресах памяти 46E и 46C (шестнадцатеричные значения).Более точная спецификация этих значений следующая:

046E - 046F  -  время дня в часах
046C - 046D  -  число отсчетов таймера с начала текущего часа
(один отсчет таймера равен 5/91 секунды)

Таким образом, для получения текущего времени необходимо осуществить привязку объекта к фиксированному адресу памяти.Для осуществления этого, можно привязать переменную Time_Hight типа Integer к фиксированному адресу 16#046E# следующим образом:

Time_Hight_Address : constant Address := To_Address (16#046E#);type Time is range 0 .. 65365; for Time'Size use 16;Time_Hight : Time; for Time_Hight'Address use Time_Hight_Address;

Следует заметить, что здесь, тип Time является беззнаковым 16-битным целым. Величина адреса 16#046E# должна иметь тип Address, который описывается в пакете System. Стандартная функция To_Address, которая выполняет преобразование целочисленного значения в значение адреса, описывается в пакете System.Storage_Elements.



Проблема предпочтительного управления



Проблема предпочтительного управления

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

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

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

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

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



Проблемы механизма рандеву



Проблемы механизма рандеву

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

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

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

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

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



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



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

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

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

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.



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



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

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

with Ada.Text_IO; use Ada.Text_IO;procedure Demo is procedure Problem_In_Scope is Cant_Be_Seen : exception; begin raise Cant_Be_Seen; end Problem_In_Scope;begin Problem_In_Scope;exception when Cant_Be_Seen => Put("just handled an_exception"); end Demo;

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

Решить эту проблему можно использованием опции others в обработчике исключений внешней процедуры Demo:

with Ada.Text_IO; use Ada.Text_IO;procedure Demo is procedure Problem_In_Scope is Cant_Be_Seen : exception; begin raise Cant_Be_Seen; end Problem_In_Scope;begin Problem_In_Scope;exception when others => Put("just handled some exception"); end Demo;

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

with Ada.Text_IO; use Ada.Text_IO;procedure Demo is Fred : exception; -- глобальное исключение ------------------------------------ procedure P1 is begin raise Fred; end P1; ------------------------------------ procedure P2 is Fred : exception; -- локальное исключение begin P1; exception when Fred => Put("wow, a Fred exception"); end P2; ------------------------------------begin P2;exception when Fred => Put("just handled a Fred exception"); end Demo;

Выводом такой процедуры будет "just handled a Fred exception". Исключение, обрабатываемое в процедуре P2, будет локально описанным исключением. Такое поведение подобно ситуации с областью видимости обычных переменных.

Для решения этой проблемы, процедуру P2 можно переписать следующим образом:

------------------------------------ procedure P2 is Fred : exception;begin P1;exception when Fred => -- локальное исключение Put("wow, an_exception"); when Demo.Fred => -- "более глобальное" исключение Put("handeled Demo.Fred exception"); raise; end P2;

Теперь, обработчик исключения процедуры P2 выдаст сообщение "handeled Demo.Fred exception" и, с помощью инструкции raise, осуществит передачу исключения Demo.Fred в обработчик исключения процедуры Demo, который, в свою очередь, выдаст сообщение "just handled a Fred exception".



Процедуры



Процедуры

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

Общий вид описания процедуры выглядит следующим образом:

procedure имя_процедуры [ (формальные_параметры) ] ;    спецификация процедуры, определяющая имя процедуры и профиль ее формальных параметров (если они есть)

Общий вид тела процедуры:

procedure имя_процедуры [ (формальные_параметры) ] is    спецификация процедуры, определяющая имя процедуры и профиль ее формальных параметров (если они есть)
      
. . .   описательная (или декларативная) часть, которая может содержать локальные описания типов, переменных, констант, подпрограмм...
      
begin     
. . .    исполняемая часть процедуры, которая описывает алгоритм работы процедуры;
обязана содержать хотя бы одну инструкцию
      
end [ имя_процедуры ];    здесь, указание имени процедуры опционально

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

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

procedure Demo(X: Integer; Y: Float) is begin null; -- пустая инструкция end Demo;

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

Demo(4, );

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



Производные типы



Производные типы

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

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

type Child_Type is new Parent_Type;

Такое описание создает новый тип данных - Child_Type, при этом Parent_Type - это тип-предок для типа Child_Type, а тип Child_Type - это производный тип от типа Parent_Type.

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

Однако, в этом случае, производный тип Child_Type - это абсолютно самостоятельный и независимый тип данных. Он не совместим ни с каким другим типом, включая тип Parent_Type, от которого он был произведен, и другими типами, производными от типа Parent_Type. Это подразумевает, что при необходимости комбинирования значений типа Child_Type со значениями типа Parent_Type требуется выполнять преобразование типов. В Аде, разрешается выполнять преобразование значений производного типа в значения типа-предка и наоборот.

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

Parent : Parent_Type; Child : Child_Type := Child_Type (Parent); -- конвертирует значение Parent, -- имеющее тип Parent_Type -- в значение типа Child_Type

Описание производного типа может указывать ограничение диапазона значений типа-предка, например:

type Child_Type is new Parent_Type range Lowerbound..Upperbound;

В этом случае диапазон значений производного типа Child_Type будет ограничен значениями нижней границы диапазона (Lowerbound) и верхней границы диапазона (Upperbound).

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

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

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

Например, класс дискретных типов предусматривает атрибут 'First, который наследуется всеми дискретными типами. Класс целочисленных типов добавляет к унаследованным от класса дискретных типов операциям знак операции арифметического сложения "+". Эти механизмы более полно рассматриваются при обсуждении тэговых типов.

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

type Employee_No is new Integer; type Account_No is new Integer range 99_999;

Здесь Employee_No и Account_No различные и не смешиваемые типы, которые нельзя комбинировать между собой без явного использования преобразования типов. Производные типы наследуют все операции объявленные для базового типа. Например, если была объявлена запись которая имела процедуры Push и Pop, то производный тип автоматически унаследует эти процедуры.

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

Это определяется использованием производных типов, без типа-предка.

type Name is range <некоторый_диапазон_значений>;

Например,

type Data is range _000_000;

В этом случае ответственность, за выбор подходящего размера для целого, ложится на компилятор. Таким образом, для PC, этот размер может быть 32 бита, что эквивалентно типу Long_Integer, а для рабочей станции Unix, этот размер можт остаться равным 32-битному целому, но это будет эквивалентно типу Integer. Такое предоставление компилятору возможности выбирать освобождает программиста от обязанности выбирать. Поэтому перекомпиляция программы на другой машине не требует изменения исходного текста.



Простое принятие обращений к входам



Простое принятие обращений к входам

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

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

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

task body Anonimous_Task is begin accept Start; . . . end Anonimous_Task;task body Simple_Task is begin . . . accept Read (Value: out Integer) do Value := Some_Value; end Read; . . . accept Request (Low) (Item: in out Integer) do Some_Low_Item := Item; . . . Item := Some_Low_Item; end Request; accept Request (Middle) (Item: in out Integer) do Some_Middle_Item := Item; . . . Item := Some_Middle_Item; end Request; accept Request (Hight) (Item: in out Integer) do Some_Hight_Item := Item; . . . Item := Some_Hight_Item; end Request; . . . end Simple_Task;

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

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

Тело задачи Simple_Task более интересно.Первая инструкция принятия рандеву соответствует входу Read с "out"-параметром Value.Внутри этой инструкции принятия рандеву параметру Value присваивается значение переменной Some_Value (предполагается, что эта и другие переменные были где-либо описаны).

Далее следуют три инструкции принятия рандеву, образующие "семейство".Все они соответствуют описанию входа Request, в спецификации задачи Simple_Task, которая описывалась с указанием типа Level, значения которого (Low, Middle и Hight) используются в качестве индекса.



Простой вызов входа



Простой вызов входа

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

declare . . . Simple_Task_Variable : Simple_Task; . . . Read_Value : Integer; Request_Item : Integer; . . . begin . . . Anonimous_Task.Start; . . . Simple_Task_Variable.Read (Read_Value); Simple_Task_Variable.Request (Middle) (Request_Item); . . . end;

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

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

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

with Ada.Text_IO;procedure Multitasking_Demo_3 is -- спецификация типа задачи task type Simple_Task (Message: Character; How_Many: Positive) is entry Start; -- этот вход будет использоваться для реального -- запуска задачи на выполнение end Simple_Task; -- тело задачи task body Simple_Task is begin -- для Simple_Task accept Start; -- в этом месте, выполнение задачи будет заблокировано -- до поступления вызова входа for Count in .How_Many loop Ada.Text_IO.Put_Line("Hello from Simple_Task " & Message); delay ; end loop; end Simple_Task; -- переменные задачного типа Simple_Task_A: Simple_Task(Message => 'A', How_Many => 5); Simple_Task_B: Simple_Task(Message => 'B', How_Many => 3);begin -- для Multitasking_Demo_3 -- в момент, когда управление достигнет этой точки, -- все задачи начнут свое выполнение, -- но будут приостановлены в инструкциях принятия рандеву Simple_Task_B.Start; Simple_Task_A.Start;end Multitasking_Demo_3;

Как видно из исходного текста этого примера, здесь описаны два объекта задач: Simple_Task_A и Simple_Task_B.Каждая задача имеет вход Start, который используется для управления очередностью запуска задач.В результате сначала запускается задача Simple_Task_B, а затем Simple_Task_A.

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

procedure Demo is X : Duration := Duration(Random(100)); Y : Duration; task Single_Entry is entry Handshake(Me_Wait : in Duration; You_Wait : out Duration); end task; task body Single_Entry is A : Duration := Duration(Random(100)); B : Duration; begin Delay A; accept Handshake(Me_Wait : in Duration; You_Wait : out Duration) do B := Me_Wait; You_Wait := A; end Handshake; Delay B; end;begin Delay(X); Handshake(X, Y); Delay(Y); end Demo;

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

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



Простые циклы (loop)



Простые циклы (loop)

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

loop -- инструкции тела циклаend loop;



Проверка типа объекта во время выполнения программы



Проверка типа объекта во время выполнения программы

В процессе выполнения программы можно осуществить проверку объекта на принадлежность его к какому-либо индивидуальному типу путем использования атрибута 'Tag

Чтобы продемонстрировать это, предположим, что реализация процедуры Show описывается следующим образом:

procedure Show (Self: in Root'Class) is begin if Self'Tag = Root'Tag then Ada.Text_IO.Put_Line ("Root"); elsif Self'Tag = Child_1'Tag then Ada.Text_IO.Put_Line ("Child_1"); elsif Self'Tag = Clild_2'Tag then Ada.Text_IO.Put_Line ("Clild_2"); elsif Self'Tag = Grand_Child_2_1'Tag then Ada.Text_IO.Put_Line ("Grand_Child_2_1"); else Ada.Text_IO.Put_Line ("Unknown type"); end if;end Show;

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

. . . if Some_Instance in Child_1'Class then . . . end if; . . .

В данном примере выполняется проверка принадлежности переменной Some_Instance к иерархии типов, корнем которой будет тип Child_1, причем, предполагается, что переменная Some_Instance является переменной надклассового или тэгового типа.

Следует также обратить внимание на то, что для выполнения этой проверки используется "Some_Instance in Child_1'Class", а не "Some_Instance in Child_1".



Проверки на равенство и на неравенство



Проверки на равенство и на неравенство

Проверки на равенство и на неравенство доступны почти для всех типов Ады. Два массива считаются равными если каждый элемент первого массива равен соответствующему элементу второго массива.

if Array1 = Array2 then....



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



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

Обычно, все объекты ссылочного типа определенные пользователем, используют один, общий для всей программы, пул динамической памяти.Однако, согласно стандарта 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;

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

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

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



Пустая инструкция



Пустая инструкция

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

null;

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



"Пустые" записи (null record) и расширения



"Пустые" записи (null record) и расширения

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

type Root is tagged record null; end record;

Для таких случаев, Ада обеспечивает специальный синтаксис описания "пустых" записей:

type Root is tagged null record;

Описание операций над таким типом традиционно, и может быть выполнено в спецификации пакета:

procedure Do_Something(Item : in out Root);

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

type Child is new Root with null record;procedure Child_Method (Item : in out Child);

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



Распространение исключений



Распространение исключений

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

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

procedure Exception_Demo is --------------------------------- procedure Level_2 is -- здесь нет обработчика исключений begin raise Constraint_Error; end Level_2; --------------------------------- procedure Level_1 is begin Level_2; exception when Constraint_Error => Put("exception caught in Level_1"); end Level_1;begin Level_1;exception when Constraint_Error => Put("exception caught in Exception_Demo"); end Exception_Demo;

После запуска этой программы будет выдано только сообщение "exception caught in Level_1".Следовательно, обработанное исключение не распространяется дальше.

Модифицируем процедуру Level_1 поместив инструкцию raise в ее обработчик исключения.Наш предыдущий пример будет иметь следующий вид:

procedure Exception_Demo is --------------------------------- procedure Level_2 is -- здесь нет обработчика исключений begin raise Constraint_Error; end Level_2; --------------------------------- procedure Level_1 is begin Level_2; exception when Constraint_Error => Put("exception caught in Level_1"); raise; -- регенерация текущего исключения; -- дает возможность другим подпрограммам -- произвести обработку возникшего -- исключения end Level_1;begin Level_1;exception when Constraint_Error => Put("exception caught in Exception_Demo"); end Exception_Demo;

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

Инструкцию raise очень удобно использовать в секции others обработчика исключений:

. . .exception . . . when others => raise; -- регенерация текущего исключения; -- дает возможность другим подпрограммам -- произвести обработку возникшего -- исключения end;

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



Расширение существующего пакета



Расширение существующего пакета

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

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

package Stacks is type Stack is private; procedure Push(Onto : in out Stack; Item : Integer); procedure Pop(From : in out Stack; Item : out Integer); function Full(Item : Stack) return Boolean; function Empty(Item : Stack) return Boolean;private -- скрытая реализация стека ... -- точка A end Stacks;package Stacks.More_Stuff is function Peek(Item : Stack) return Integer;end Stacks.More_Stuff;

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

В показанном выше примере, пакет Stacks.More_Stuff является дочерним пакетом для пакета Stacks.Значит, дочерний пакет Stacks.More_Stuff "видит" все описания пакета Stacks, вплоть до точки A.

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

Примечание:
Согласно правил именования файлов, принятым в системе компилятора GNAT, спецификация и тело пакета Stacks должны быть помещены в файлы:
stacks.ads и stacks.adb
соответственно, а спецификация и тело дочернего пакета Stacks.More_Stuff - в файлы:
stacks-more_stuff.ads и stacks-more_stuff.adb

Клиенту, которому необходимо использовать функцию Peek, просто необходимо включить дочерний пакет в инструкцию спецификатора совместности контекста with:

with Stacks.More_Stuff;procedure Demo is X : Stacks.Stack;begin Stacks.Push(X, 5); if Stacks.More_Stuff.Peek = 5 then . . .end Demo;

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

with Stacks.More_Stuff; use Stacks; use More_Stuff;procedure Demo is X : Stack;begin Push(X, 5); if Peek(x) = 5 then . . .end Demo;

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



Расширение существующего типа данных



Расширение существующего типа данных

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

type Object_1 is tagged record Field_1 : Integer; end record;

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

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

type Object_2 is new Object_1 with record Field_2 : Integer; end record;type Object_3 is new Object_2 with record Field_3 : Integer; end record;

В данном примере, тип Object_2 является производным типом от типа Object_1, а тип Object_3 - производным типом от типа Object_2.Таким образом, в результате показанных описаний, получилась следующая иерархия типов:

Object_1 | Object_2 | Object_3

Следует заметить, что тип, производный от тэгового типа, также будет являться тэговым типом.Поэтому все описанные выше типы: Object_1, Object_2 и Object_3, - являются тэговыми типами, и, следовательно, обладают способностью расширения.

В результате показанных выше описаний, типы Object_1, Object_2 и Object_3 будут содержать следующие поля:

Object_1 Object_2 Object_3
Field_1 Field_1 Field_1
Field_2 Field_2
    Field_3

Примечательным фактом является то, что описание типа Object_2 не указывает явно наличие поля Field_1.Это поле наследуется от типа Object_1.Также, описание типа Object_3 не указывает явно наличие полей Field_1 и Field_2.Эти поля наследуются от типа Object_2.



Раздельная компиляция



Раздельная компиляция

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

В первом файле:

with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;procedure Ive_Got_A_Procedure is X : Integer := 6; Y : Integer := 5; procedure Display_Values(Number : Integer) is separate;begin Display_Values(X); Display_Values(Y); end Ive_Got_A_Procedure;

Во втором файле:

separate(Ive_Got_A_Procedure) -- примечание! нет завершайщего символа -- точки с запятойprocedure Display_Values(Number : Integer) is begin Put(Number); New_Line; end Display_Values;

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



Разделяемые (общие) переменные



Разделяемые (общие) переменные

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

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

В стандарте Ada95 для указания разделяемых переменных используются следующие директивы компилятора:

pragma Atomic ( Local_Name ); pragma Atomic_Components ( Local_Array_Name );pragma Volatile ( Local_Name ); pragma Volatile_Components ( Local_Array_Name );

Здесь Local_Name указывает локальное имя объекта или описание типа, а Local_Array_Name указывает локальное имя объекта-массива или описание типа-массива.

Директивы компилятора Atomic и Atomic_Components обеспечивают непрерывность операций чтения/записи для всех указанных в них объектах.Такие объекты называют атомарными (atomic), а операции над ними выполняются только последовательно.

Директивы компилятора Volatile и Volatile_Components обеспечивают выполнение операций чтения/записи для всех указанных в них объектах непосредственно в памяти.

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

Array_Size : Positive; pragma Atomic (Array_Size); pragma Volatile (Array_Size);Store_Array is array (.Array_Size) of Integer; pragma Atomic_Components (Store_Array); pragma Volatile_Components (Store_Array);

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

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



Режим access



Режим access

Поскольку значения ссылочного типа (указатели) часто используются в качестве параметров передаваемых подпрограммам, Ада предусматривает режим передачи параметров access, который специально предназначен для передачи параметров ссылочного типа.Заметим, что подробному обсуждению ссылочных типов Ады далее посвящена самостоятельная глава - "Ссылочные типы (указатели)".Необходимо также обратить внимание на то, что режим передачи параметров access был введен стандартом Ada95 и он отсутствует в стандарте Ada8

При использовании режима access, фактический параметр, который предоставляется при вызове подпрограммы, - это любое значение ссылочного типа, которое ссылается (указывает) на объект соответствующего типа.При входе в подпрограмму, формальный параметр инициализируется значением фактического параметра, при этом, Ада производит автоматическую проверку того, что значение параметра не равно null.В случае когда значение параметра равно null генерируется исключительная ситуация Constraint_Error (проще говоря, - ошибка).Внутри подпрограммы, формальный параметр, использующий режим access, является константой ссылочного типа и ему нельзя присваивать новое значение, поэтому такие формальные параметры несколько подобны формальным параметрам, использующим режим "in".Однако, поскольку параметр является значением ссылочного типа (указателем), то подпрограмма может изменить содержимое объекта на который данный параметр ссылается (указывает).Кроме того, внутри подпрограммы такой параметр принадлежит анонимному ссылочному типу, и поскольку у нас нет возможности определить имя этого ссылочного типа, то мы не можем описать ни одного дополнительного объекта этого типа.Любая попытка конвертирования значения такого параметра в значение именованого ссылочного типа будет проверяться на соответствие правилам области действия для ссылочных типов. При обнаружении нарушения этих правил генерируется исключительная ситуация Programm_Error.

. . .function Demo_Access(A : access Integer) return Integer is begin return A.all; end Demo_Access; . . .type Integer_Access is access Integer;Integer_Access_Var : Integer_Access := new Integer'(1); Aliased_Integer_Var : aliased Integer; . . .X : Integer := Demo_Access(Integer_Access_Var); Y : Integer := Demo_Access(Aliased_Integer_Var'Access); Z : Integer := Demo_Access(new Integer); . . .

Режим access разрешается использовать и в процедурах, и в функциях.

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



Режим "in"



Режим "in"

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

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

with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;procedure Demo(X : in Integer; Y : in Integer) is begin X := 5; -- недопустимо, in параметр доступен только по чтению Put(Y); Get(Y); -- также недопустимо end Demo;

Режим "in" разрешается использовать и в процедурах, и в функциях.



Режим "in out"



Режим "in out"

Этот режим непосредственно соответствует параметрам передаваемым по ссылке (подобно var-параметрам языка Паскаль).

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

procedure Demo(X : in out Integer; Y : in Integer) is Z : constant Integer := X;begin X := Y * Z; -- это допустимо! end Demo;

Режим "in out" разрешается использовать только в процедурах.



Режим "out"



Режим "out"

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

procedure Demo(X : out Integer; Y : in Integer) is -- при входе в подпрограмму X не инициализирован!!!begin X := Y; end Demo;

Режим "out" разрешается использовать только в процедурах.



Режимы передачи параметров



Режимы передачи параметров

Стандарт Ada83 предусматривал три режима передачи параметров для подпрограмм: "in"
"in out"
"out"

Стандарт Ada95 добавил еще один режим передачи параметров: access

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

по-умолчанию, для передачи параметров подпрограммы, всегда устанавливается режим - "in" !!!

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

Ada95 указывает, что лимитированные приватные типы (limited private types), которые рассматриваются позднее, передаются по ссылке, для предотвращения проблем нарушения приватности.



Счетчик использования



Счетчик использования

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

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

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

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

with Ada.Text_IO; with Ada.Finalization; use Ada.Finalization;package Log is type Item is private; procedure Put_To_Log (Self: in out Item; S: in String);private type Item is new Limited_Controlled with record . . . -- описание полей расширения end record; procedure Initialize (Self: in out Item); procedure Finalize (Self: in out Item); The_Log_File: Ada.Text_IO.File_Type; The_Counter: Natural := 0;end Log;

Здесь тип Item описывает тип объектов при обработке которых используется общий файл протокола The_Log_File.Для вывода информации о состоянии объекта типа Item в файл протокола The_Log_File используется процедура Put_To_Log.Для подсчета количества существующих в текущий момент объектов типа Item используется переменная The_Counter.

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

package body Log is procedure Initialize (Self: in out Item) is begin The_Counter := The_Counter + 1; if The_Counter = 1 then Ada.Text_IO.Open (File => The_Log_File, Mode => Ada.Text_IO.Append_File, Name => "log.txt"); end if; end Initialize; procedure Finalize (Self: in out Item) is begin if The_Counter = 1 then Ada.Text_IO.Close (The_Log_File); end if; The_Counter := The_Counter - 1; end Finalize; procedure Put_To_Log (Self: in out Item; S: in String) is begin . . . -- вывод необходимых данных в файл The_Log_File end Put_To_Log;end Log;

Как видно из примера, открытие файла протокола The_Log_File, при создании первого объекта типа Item, и инкремент количества существующих в текущий момент объектов типа Item в переменной The_Counter выполняется автоматически вызываемой процедурой Initialize.Декремент количества существующих в текущий момент объектов типа Item в переменной The_Counter и закрытие файла протокола The_Log_File выполняется автоматически вызываемой процедурой Finalize.

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



Селекция принятия рандеву



Селекция принятия рандеву

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

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

task Server_Task is entry Service_1 [ параметры для Service_1 ] ; entry Service_2 [ параметры для Service_2 ] ; . . . entry Service_N [ параметры для Service_N ] ; entry Stop; end task;task body Server_Task is . . . begin loop accept Service_1 [ параметры для Service_1 ] do . . . end Service_1; accept Service_2 [ параметры для Service_2 ] do . . . end Service_2; . . . accept Service_N [ параметры для Service_N ] do . . . end Service_N; accept Stop do exit ; -- выход из цикла, и, следовательно, -- завершение задачи end Stop; end loop; end Server_Task;

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

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

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

task Server_Task is entry Service_1 [ параметры для Service_1 ] ; entry Service_2 [ параметры для Service_2 ] ; . . . entry Service_N [ параметры для Service_N ] ; entry Stop; end task;task body Server_Task is . . . begin loop select accept Service_1 [ параметры для Service_1 ] do . . . end Service_1; . . . -- дополнительная последовательность инструкций, -- которая выполняется после принятия рандеву -- на входе Service_1 or accept Service_2 [ параметры для Service_2 ] do . . . end Service_2; or . . . or accept Service_N [ параметры для Service_N ] do . . . end Service_N; or accept Stop; exit ; -- выход из цикла, и, следовательно, -- завершение задачи end select end loop; end Server_Task;

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

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

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

В данном примере интересную ситуацию представляет альтернатива принятия рандеву на входе Stop.В этом случае происходит выход из бесконечного цикла выполнения инструкции отбора, что, в свою очередь, приводит к завершению работы задачи-сервера.Если в процессе завершения работы задачи-сервера поступит вызов рандеву на какой-либо из входов Service_1 - Service_N, то задача-клиент, вызывающая рандеву, скорее всего получит исключение Tasking_Error.Недостаток такого подхода состоит в том, что завершение работы задачи-сервера требует явного указания вызова рандеву на входе Stop.

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

task Server_Task is entry Service_1 [ параметры для Service_1 ] ; . . . entry Service_N [ параметры для Service_N ] ; end task;task body Server_Task is . . . begin loop select accept Service_1 [ параметры для Service_1 ] do . . . end Service_1; or . . . or accept Service_N [ параметры для Service_N ] do . . . end Service_N; or terminate; -- завершение работы задачи end select end loop; end Server_Task;

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

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

loop select accept Service_1 [ параметры для Service_1 ] do . . . end Service_1; or . . . or accept Service_N [ параметры для Service_N ] do . . . end Service_N; else . . . -- последовательность инструкций, которая выполняется -- если нет ни одного вызова рандеву end select end loop;

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

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

loop select accept Service_1 [ параметры для Service_1 ] do . . . end Service_1; or . . . or accept Service_N [ параметры для Service_N ] do . . . end Service_N; or delay ; . . . -- последовательность инструкций таймаута, -- которая выполняется в случае -- когда нет ни одного вызова рандеву -- в течение одной секунды end select end loop;

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

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

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

При применении инструкций отбора следует учитывать, что использование альтернативы завершения работы задачи (terminate), альтернативы таймаута (delay) и раздела else в пределах одной инструкции отбора является взаимно исключающим.Кроме того, любой вариант инструкции отбора обязан содержать хотя бы одну альтернативу выбора, в которой указана инструкция принятия рандеву accept.

Еще одной особенностью инструкций отбора является опциональная возможность указания дополнительной проверки какого-либо условия для альтернатив принятия рандеву, завершения работы задачи и таймаута.Для указания такой проверки используется конструкция вида: "when условие =>", - где проверяемое условие описывается с помощью логического выражения, результат вычисления которого должен иметь значение предопределенного логического типа Standard.Boolean.Как правило, такую проверку назвают защитной или охранной (guard), а ее использование может иметь следующий вид:

declare . . . Service_1_Counter : Integer; . . . Service_N_Counter : Integer; . . . begin . . . loop . . . select when (Service_1_Counter > 0) => accept Service_1 [ параметры для Service_1 ] do . . . end Service_1; or . . . or when (Service_N_Counter > 100) => accept Service_N [ параметры для Service_N ] do . . . end Service_N; end select end loop; . . . end;

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

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



Селекция вызова рандеву



Селекция вызова рандеву

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

Рассмотрим простой пример инструкции отбора для выполнения условного вызова рандеву на входе Service_1 задачи-сервера Server_Task:

select Server_Task.Service_1 [ параметры для Service_1 ] ; else Put_Line ("Server_Task is busy!"); end select;

В этом случае, если задача-сервер Server_Task не готова к немедленному приему рандеву на входе Service_1, то вызов рандеву будет отменен и выполнится последовательность инструкций в разделе else, которая выводит сообщение "Server_Task is busy!".

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

loop select Server_Task.Service_1 [ параметры для Service_1 ] ; . . . -- опциональная последовательность инструкций, exit; -- выполняемая после рандеву else . . . -- последовательность инструкций, -- выполняемая в случае невозможности -- осуществления рандеву end select;end loop;

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

select Server_Task.Service_1 [ параметры для Service_1 ] ; or delay ; Put_Line ("Server_Task has been busy for 5 seconds!"); end select;

В данном случае, если в течение указанного интервала времени (здесь задан относительный интервал времени длительностью 5 секунд) задача-сервер Server_Task не приняла рандеву на входе Service_1, то вызов рандеву отменяется и выполняется последовательность инструкций, заданная альтернативой задержки, которая в этом примере выдаст сообщение "Server_Task has been busy for 5 seconds!".

Вместо относительного интервала может быть указано абсолютное значение времени:

select Server_Task.Service_1 [ параметры для Service_1 ] ; or delay until Christmas; Put_Line ("Server_Task has been busy for ages!"); end select;

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

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

loop select Server_Task.Service_1 [ параметры для Service_1 ] ; . . . -- опциональная последовательность инструкций, exit; -- выполняемая после рандеву or delay интервал_времени . . . -- последовательность инструкций, -- выполняемая в случае невозможности -- осуществления рандеву end select;end loop;

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

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

select delay ; Put_Line ("Server_Task is not serviced the Service_1 yet"); then abort Server_Task.Service_1 [ параметры для Service_1 ] ; end select;

В данном случае начинается выполнение инструкций, расположенных между "then abort" и "end select", то есть вызов рандеву с задачей-сервером Server_Task на входе Service_1.При этом, если истечение интервала времени из инструкции delay (или delay until), расположенной после select, произойдет раньше завершения выполнения Server_Task.Service_1, то выполнение Server_Task.Service_1 принудительно прерывается и выполняется вывод сообщения "Server_Task is not serviced the Service_1 yet".

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

select delay ; Put_Line ("So_Big_Calculation abandoned!"); then abort So_Big_Calculation; end select;

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

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



Символьные типы Ады (Character, Wide_Character)



Символьные типы Ады (Character, Wide_Character)

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

Оригинальный стандарт Ada83 описывал 7-битный тип Character. Еще до появления стандарта Ada95, это ограничение было ослаблено, но оставалось принудительным для старых компиляторов (например таких как компилятор Meridian Ada). Это создавало трудности при попытках отобразить графические символы на PC, поскольку для отображения символов с кодами большими чем ASCII-127 приходилось использовать целые числа. Такая поддержка обеспечивалась за счет специальных подпрограмм предоставляемых разработчиками соответствующего компилятора.

В настоящее время, предопределенный символьный тип Character предусматривает 256 различных символьных значений (то есть, является 8-битным), и основывается на стандарте ISO-8859-1 (Latin-1).

Некоторые символы не имеют непосредственно печатаемого значения (первые 32 символа). Такие символы используются в качестве управляющих (примером может служить символ CR - возврат каретки). Для обращения к таким символам можно использовать пакет ASCII, который является дочерним пакетом пакета Standard (благодаря этому, нет необходимости указывать пакет ASCII в спецификаторах контекста with и/или use). Например, для обращения к символу возврат каретки можно использовать: ASCII.CR. Однако, пакет ASCII содержит только первые 128 символов и считается устаревшим, и возможно, что в будущем он будет удален. Поэтому, вместо старого пакета ASCII рекомендуется использовать пакет Ada.Characters.Latin_1, который предоставляет 256 символов. Следовательно, используя пакет Ada.Characters.Latin_1, к символу возврата каретки можно обратиться следующим образом: Ada.Characters.Latin_CR.

Предопределенный символьный тип Wide_Character основывается на стандарте ISO-10646 Basic Multilingual Plane (BMP) и предусматривает 65336 различных символьных значений (использует 16 бит).

Также, Ада предоставляет пакет Ada.Characters.Handling, предлагающий набор полезных подпрограмм символьной обработки.

Система компилятора GNAT предоставляет дополнительный пакет Ada.Characters.Wide_Latin_1, который описывает символьные значения типа Wide_Character соответствующие кодировке Latin_

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

Standard.ASCII -- предоставляет только первые 128 символов -- (считается устаревшим)Ada.Characters -- Ada.Characters.Latin_1 -- предоставляет 256 символов ISO-8859-1 (Latin-1) Ada.Characters.Handling -- предоставляет подпрограммы символьной обработкиAda.Characters.Wide_Latin_1 -- дополнительный пакет из поставки -- системы компилятора GNAT

Следует заметить, что пакет ASCII считается устаревшим и его не рекомендуется использовать при написании новых программ. Вместо него необходимо использовать стандартный пакет Ada.Characters.Latin_1.

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



Скалярные типы данных языка Ада.



Скалярные типы данных языка Ада.

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

Предопределенный пакет Standard содержит описания стандартных типов, таких как Integer, Float, Boolean, Character и Wide_Character, а также определяет операции, которые допускается производить над этими типами.

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

=, /= проверка на равенство/не равенство <, <=, >, >= меньше, меньше или равно, больше, больше или равно in, not in проверка принадлежности к диапазону

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



Смешивание позиционного и именованного сопоставления



Смешивание позиционного и именованного сопоставления

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

procedure Square(Result : out Integer; Number : in Integer) is begin Result := Number * Number; end Square;

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

Square(X, 4); Square(X, Number => 4); Square(Result => X, Number => 4); Square(Number => 4, Result => x);Square(Number => 4, X); -- недопустимо, поскольку позиционно-ассоциируемый -- параметр следует за параметром, ассоциируемым -- по имени



Совмещение (overloading)



Совмещение (overloading)

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

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



Совмещение подпрограмм (subprogram overloading)



Совмещение подпрограмм (subprogram overloading)

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

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

procedure Insert(Item : Integer); -- две процедуры с одинаковыми именами, procedure Insert(Item : Float); -- но имеющие разный "профиль"

Примерами совмещенных подпрограмм могут служить процедуры Put и Get из стандартного пакета Ada.Text_IO.



Совмещение знаков операций (operator overloading)



Совмещение знаков операций (operator overloading)

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

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

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

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

procedure Add_Demo is type Vector is array (Positive range <>) of Integer; A : Vector(); B : Vector(); C : Vector(); function "+"(Left, Right : Vector) return Vector is Result : Vector(Left'First..Left'Last); Offset : constant Natural := Right'First - 1; begin if Left'Length /= Right'Length then raise Program_Error; -- исключение, -- рассматриваются позже end if; for I in Left'Range loop Result(I) := Left(I) + Right(I - Offset); end loop; return Result; end "+";begin A := (1, 2, 3, 4, 5); B := (1, 2, 3, 4, 5); C := A + B; end Add_Demo;

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



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



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

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

Объект задачи может быть создан в процессе элаборации описания какого-либо объекта, расположенного где-либо в описательной части, или в процессе обработки аллокатора (выражение в виде "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 := 4; 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 := * 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 := * 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; -- двусмысленность, мы ссылаемся -- на Noc или на Noc? NoC := 3; -- избавление от двусмысленности путем возврата NoC := 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; -- удаление двусмысленности путем указания -- имени процедуры в точечной нотации NoA := 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 будет 3

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

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 type"

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

use type имя_типа

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

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



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

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

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

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



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



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

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

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

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