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



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

         

Особенности программирования защищенных входов и подпрограмм



Особенности программирования защищенных входов и подпрограмм

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

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


является также потенциально блокирующим.

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

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



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



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

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

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

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

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

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

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

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

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

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

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

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

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

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

. . . Free_List(Head_Element); . . .

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

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

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

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



Отладка контролируемых типов Некоторые рекомендации



Отладка контролируемых типов
       Некоторые рекомендации

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

procedure Initialize (Self: in out Controlled_Type) is begin . . . -- код инициализации Ada.Text_IO.Put_Line("Initialize called for Controlled_Type"); end Initialize;procedure Finalize (Self: in out Controlled_Type) is begin . . . -- код очистки Ada.Text_IO.Put_Line("Finalize called for Controlled_Type"); end Finalize;procedure Adjust (Self: in out Controlled_Type) is begin . . . -- код подгонки Ada.Text_IO.Put_Line("Adjust called for Controlled_Type"); end Adjust;

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

Перечислим также некоторые рекомендации которые помогут избежать некоторых ошибок при написании процедур Initialize, Finalize и Adjust:

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

Подпрограммы Finalize и Adjust должны быть симметричны и инверсны по отношению друг к другу.

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

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

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

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

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



Отложенные константы (deferred constants)



Отложенные константы (deferred constants)

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

package Coords is type Coord is private; Home: constant Coord; -- отложенная константа!private type Coord is record X : Integer; Y : Integer; end record; Home : constant Coord := (0, 0); end Coords;

В результате такого описания, пользователи пакета Coords "видят" константу Home, которая имеет приватный тип Coord, и могут использовать эту константу в своем коде.При этом, детали внутреннего представления этой константы им не доступны и они могут о них не заботиться.



Отрезки (array slices)



Отрезки (array slices)

Для одномерных массивов Ада предусматривает удобную возможность указания нескольких последовательных компонент массива. Такая последовательность компонент массива называется отрезком массива (array slice). В общем случае, отрезок массива может быть задан следующим образом:

<имя_массива> (<диапазон_значений_индекса>)

Таким образом, для переменной Calculator_Workspace типа Stack, рассмотренных ранее, можно указать отрезок, содержащий элементы с 5-го по 10-й, следующим образом:

Calculator_Workspace (5 .. 10) := (5, 6, 7, 8, 9, 10);

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

Приведем еще один простой пример:

Calculator_Workspace (25 .. 30) := Calculator_Workspace (5 .. 10);

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



Пакет Ada.Direct_IO



Пакет Ada.Direct_IO

Пакет Ada.Direct_IO построен поверх пакета Ada.Sequential_IO.Он предусматривает возможность прямого обращения к необходимой записи в файле, определения размера файла и определения текущего индекса.Кроме этого, он дополнительно позволяет открывать файлы в режиме - Inout_File (чтение/запись).Такие средства, в совокупности с подходящим индексирующим пакетом, должны позволять построение пакета файловой обработки очень высокого уровня.

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

with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;with Ada.Direct_IO; -- настраиваемый пакетwith Personnel_Details; -- имеет: use Personnel_Details; -- тип записи "Personnel", -- процедуру "Display_Personnel", -- и т.д. ...with Display_Menu; -- внешняя процедура отображения менюprocedure Direct_Demo is package Person_IO is new Direct_IO(Personnel); Data_File : Person_IO.File_type; A_Person : Personnel; Option : Integer; Employee_No : Integer;begin Person_IO.Open(Data_File, Inout_File, "Person.dat"); loop Display_Menu; Get_Option(Option); case Option is when 1 => Get(Employee_No); Set_Index(Positive_Count(Employee_No)); Read(Data_File, A_Person); Display_Person(A_Person); when 2 => Get(Employee_No); Set_Index(Positive_Count(Employee_No)); Read(Data_File, A_Person); Get_New_Details(A_Person); Write(Data_File, A_Person); when 3 => exit; when others => Put("not a great option!"); end case; end loop; Close(Data_File); end Direct_Demo;

Здесь, для краткости подразумевается, что записи о служащих сохраняются в порядке номеров служащих - Employee_No.

Также заметим, что мы не акцентируем внимание на содержимом внешних модулей: пакете Personnel_Details и процедуре Display_Menu.



Пакет Ada.Exceptions



Пакет Ada.Exceptions

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

Описанный в нем объект:

Event : Exception_Occurence;

и подпрограммы:

функция Exception_Name(Event) возвращает строку имени исключения, начиная от корневого библиотечного модуля
   
функция Exception_Information(Event)  -  возвращает строку детальной информации о возникшем исключении
  
функция Exception_Message(Event)  -  возвращает строку краткого объяснения исключения
  
процедура Reraise_Occurence(Event)  -  выполняет повторное возбуждение исключения Event
  
процедура Reraise_Exception(e, "Msg")  -  выполняет возбуждение исключения e с сообщением "Msg"

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

. . .exception . . . when The_Event: others => Put("Unexpected exeption is "; Put(Exeption_Name(The_Event)); New_Line;



Пакет Ada.Interrupts



Пакет Ada.Interrupts

Не-вложенная установка и удаление обработчиков прерываний полагается на дополнительные средства стандартного пакета Ada.Interrupts спецификация которого имеет следующий вид:

package Ada.Interrupts is type Interrupt_ID is Определяется_Реализацией; type Parameterless_Handler is access protected procedure; function Is_Reserved (Interrupt : Interrupt_ID) return Boolean; function Is_Attached (Interrupt : Interrupt_ID) return Boolean; function Current_Handler (Interrupt : Interrupt_ID) return Parameterless_Handler; procedure Attach_Handler (New_Handler : in Parameterless_Handler; Interrupt : in Interrupt_ID); procedure Exchange_Handler (Old_Handler : in out Parameterless_Handler; New_Handler : in Parameterless_Handler; Interrupt : in Interrupt_ID); procedure Detach_Handler (Interrupt : in Interrupt_ID); function Reference (Interrupt : Interrupt_ID) return System.Address;private . . . -- стандартом не определеноend Ada.Interrupts;

Процедура Attach_Handler используется для установки соответствующего обработчика прерывания, переопределяя любой существующий обработчик (включая обработчик пользователя).Если параметр New_Handler - null, то осуществляется восстановление обработчика по умолчанию.Если параметр New_Handler указывает защищенную процедуру для которой не была применена директива компилятора Interrupt_Handler, то возбуждается исключение Programm_Error.



Пакет Ada.Sequential_IO



Пакет Ada.Sequential_IO

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

Базовое содержимое настраиваемого пакета Ada.Sequential_IO идентично пакету Ada.Text_IO, за исключением того, что процедуры Get и Put соответственно заменены процедурами Read и Write, и эти процедуры будут работать с типом данных для которого была произведена конкретизация настраиваемого пакета.Кроме этого, отсутствует понятие строки текста, и, следовательно, нет функции End_Of_Line и процедур Skip_Line, New_Line.

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

with Ada.Sequential_IO; -- настраиваемый пакетwith Personnel_Details; -- имеет тип записи "Personnel" use Personnel_Details;with Produce_Retirement_Letter;procedure Sequential_Demo is package Person_IO is new Ada.Sequential_IO(Personnel); Data_File : Person_IO.File_type; A_Person : Personnel;begin Person_IO.Open(Data_File, In_File, "person.dat"); while not Person_IO.End_Of_File(Data_File) loop Person_IO.Read(Data_File, A_Person); if A_Person.Age > 100 then Produce_Retirement_Letter(A_Person); end if; end loop; Close(Data_File); end Sequential_Demo;

Заметим, что в данном примере мы не акцентируем внимание на содержимом пакета Personnel_Details, а только указываем в комментарии, что он описывает тип записи Personnel.

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



Пакет Ada.Text_IO



Пакет Ada.Text_IO

Основой организации текстового ввода/вывода Ады является пакет Ada.Text_IO и коллекция его дочерних пакетов.Этот пакет обеспечивает средства, которые позволяют манипулировать текстовыми файлами.Примерами таких средств могут служить подпрограммы Close, Delete, Reset, Open, Create ...

Главный тип данных пакета Ada.Text_IO - это лимитированный приватный тип File_Type. Он является внутренним представлением файла.Стандарт Ada95 добавил тип File_Access, как ссылку на тип File_Type (объекты имеющие тип File_Access часто называют дескрипторами файлов).При открытии или создании файла, производится ассоциирование между именем файла в системе и объектом типа File_Type. Кроме того, при открытии или создании файла, необходимо указывать режим доступа к файлу:

In_File чтение файла
Out_File  -  запись в файл
Append_File  -  запись в конец существующего файла (Ada95)

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

В приведенном ниже примере, процедура Create создает файл "first_file.dat", после чего, в этот файл процедурами Put и New_Line производится запись строки "It is my first text file!".В завершение, процедура Close закрывает ранее открытый файл.

with Ada.Text_IO; use Ada.Text_IO;procedure Demo_File_IO is My_File : Ada.Text_IO.File_type;begin Create(File => My_File, Mode => Out_File, Name => "first_file.dat"); Put(File => My_File, Item => "It is my first text file!"); New_Line(My_File); Close(My_File); -- требуется! Ада может не закрыть -- открытый вами файл end Demo_File_IO;

Программа, представленная в следующем примере, выполняет посимвольное чтение данных из одного файла ("input.dat") и посимвольную запись этих же данных в другой файл ("output.dat").

with Ada.Text_IO; use Ada.Text_IO;procedure Read_Write is Input_File : File_type; Output_File : File_type; Char : Character;begin Open(Input_File, In_File, "input.dat"); Create(Output_File, Out_File, "output.dat"); while not End_Of_File(Input_File) loop while not End_Of_Line(Input_File) loop Get(Input_File, Char); Put(Output_File, Char); end loop; Skip_Line(Input_File); New_Line(Output_File); end loop; Close(Input_File); Close(Output_File); end Read_Write;

Необходимо обратить внимание на то, что в таких языках программирования как Ада и Паскаль существует концепция терминатора строки, который не является обычным символом файла.Это значит, что понятие "конец строки" ("End Of Line", или сокращенно - EOF) Ады отличается от того, что принято в системах DOS, Windows и UNIX.В этих системах для обозначения конца строки используется обычный символ (символ "CR" - для UNIX, и символы: "CR", "LF" - для DOS и Windows), который может быть обработан обычными средствами символьной обработки.

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

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

Create  -  Создает файл с указанным именем и режимом использования. Примечательно, что если файл имеет строку null, то файл является временным и позднее будет удален.
Open  -  Открывает файл с указанным именем и режимом использования.
Delete  -  Удаляет указанный файл. При попытке удалить открытый файл происходит ошибка.
Reset  -  Возвращает позицию чтения (или записи) в начало файла.

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

End_of_File  -  Возвращает истину если мы находимся в конце текущего файла.
End_of_Line  -  Возвращает истину если мы находимся в конце текущей строки текста.
Is_Open  -  Возвращает истину если текущий файл открыт.
Mode  -  Возвращает режим использования текущего файла.
Name  -  Возвращает строку имени текущего файла.

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



Пакеты



Пакеты

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

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



Пакеты для численной обработки



Пакеты для численной обработки

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

Ada.Numerics Ada.Numerics.Aux Ada.Numerics.Float_Random Ada.Numerics.Discrete_RandomAda.Numerics.Complex_Types Ada.Numerics.Complex_Elementary_Functions Ada.Numerics.Elementary_FunctionsAda.Numerics.Generic_Complex_Types Ada.Numerics.Generic_Complex_Elementary_Functions Ada.Numerics.Generic_Elementary_FunctionsAda.Numerics.Long_Complex_Types Ada.Numerics.Long_Complex_Elementary_Functions Ada.Numerics.Long_Elementary_FunctionsAda.Numerics.Long_Long_Complex_Types Ada.Numerics.Long_Long_Complex_Elementary_Functions Ada.Numerics.Long_Long_Elementary_FunctionsAda.Numerics.Short_Complex_Types Ada.Numerics.Short_Complex_Elementary_Functions Ada.Numerics.Short_Elementary_Functions

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



Параметры-подпрограммы



Параметры-подпрограммы

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

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

generic type Element is limited private; with function "="(E1, E2 : Element) return Boolean; with procedure Assign(E1, E2 : Element);package Stuff is . . .

Конкретизация такого настраиваемого модуля может иметь вид:

package Things is new Stuff(Person, Text."=", Text.Assign);

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

procedure My_Assign(E1, E2 : Person);

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

generic type Element is limited private; with function "="(E1, E2 : Element) return Boolean; with procedure Assign(E1, E2 : Element) is My_Assign(E1, E2 : Person);package Stuff is . . .

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

package Things is new Stuff(Person, Text."=");

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

generic type Element is limited private; with function "="(E1, E2 : Element ) return Boolean is <>; . . .

Теперь, если при конкретизации настраиваемого модуля для функции "=" не будет представлена соответствующая функция, то будет использоваться функция проверки на равенство по-умолчанию, выбранная в соответствии с фактическим типом Element.Например, если фактический тип Element - тип Integer, то будет использоваться обычная, для типа Integer, функция "=".



Параметры-типы



Параметры-типы

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

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

type T is limited private -- тип T - любой тип type T is private -- тип T - любой не лимитированный типtype T is (<>) -- тип T любой дискретный тип -- (целочисленный или перечислимый) type T is range <> -- тип T любой целочисленный тип type T is mod <> -- тип T любой модульный целочисленный типtype T is digits <> -- тип T любой вещественный тип с плавающей точкой type T is delta <> -- тип T любой вещественный тип с фиксированной точкой type T is delta <> digits <> -- тип T любой вещественный децимальный типtype T is access Y; -- тип T любой ссылающийся на Y ссылочный тип type T is access all Y; -- тип T любой "access all Y" ссылочный тип type T is access constant Y; -- тип T любой "access constant Y" ссылочный тип -- примечание: тип Y может быть предварительно описанным -- настраиваемым параметромtype T is array(Y range <>) of Z; -- тип T любой неограниченный массив элементов типа Z -- у которого Y - подтип индекса type T is array(Y) of Z; -- тип T любой ограниченный массив элементов типа Z -- у которого Y - подтип индекса -- примечание: тип Z (тип компонента фактического массива) -- должен совпадать с типом формального массива. -- если они не являются скалярными типами, -- то они оба должны иметь тип -- ограниченного или неограниченного массиваtype T is new Y; -- тип T любой производный от Y тип type T is new Y with private; -- тип T любой не абстрактный тэговый тип -- производный от Y type T is abstract new Y with private; -- тип T любой тэговый тип производный от Y type T is tagged private; -- тип T любой не абстрактный не лимитированый -- тэговый тип type T is tagged limited private; -- тип T любой не абстрактный тэговый тип type T is abstract tagged private; -- тип T любой не лимитированый тэговый тип type T is abstract tagged limited private; -- тип T любой тэговый тип

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

generic type Item is private; type Index is (<>); type Vector is array (Index range <>) of Item; type Table is array (Index) of Item; package P is . . .

и типы:

type Color is (red, green, blue); type Mix is array (Color range <> ) of Boolean; type Option is array (Color) of Boolean;

тогда, Mix может соответствовать Vector, а Option может соответствовать Table.

package R is new P( Item => Boolean, Index => Color, Vector => Mix, Table => Option);



Параметры-значения



Параметры-значения

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

generic type Element is private; Size: Positive := 200;package Stacks is procedure Push... procedure Pop... function Empty return Boolean;end Stacks;package body Stacks is Size : Integer; theStack : array(.Size) of Element; . . .

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

package Fred is new Stacks(Element => Integer, Size => 50);package Fred is new Stacks(Integer, 1000);package Fred is new Stacks(Integer);

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

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

generic type Element is private; File_Name : String;package ....

Примечательно, что параметр File_Name, имеющий строковый тип String, - не ограничен (not constrained).Это идентично строковым параметрам для подпрограмм.



Параметры командной строки



Параметры командной строки

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

package Ada.Command_Line is pragma Preelaborate (Command_Line); function Argument_Count return Natural; function Argument (Number : in Positive) return String; function Command_Name return String; type Exit_Status is Определяемый_Реализацией_Целочисленный_Тип; Success : constant Exit_Status; Failure : constant Exit_Status; procedure Set_Exit_Status (Code : in Exit_Status);private -- Стандартом языка не определено end Ada.Command_Line;

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

with Ada.Text_IO; with Ada.Command_Line;procedure Show_CMDLine is begin Ada.Text_IO.Put_Line ("The program " & '"' & Ada.Command_Line.Command_Name & '"' & " has " & Ada.Command_Line.Argument_Count'Img & " argument(s):"); for I in .Ada.Command_Line.Argument_Count loop Ada.Text_IO.Put_Line (" The argument " & I'Img & " is " & '"' & Ada.Command_Line.Argument (I) & '"'); end loop;end Show_CMDLine;

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



Параметры настройки для настраиваемых модулей



Параметры настройки для настраиваемых модулей

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

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



Перечислимые типы



Перечислимые типы

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



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



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

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

with Graphics.Common_Display_Types; package CDT renames Graphics.Common_Display_Types;

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

Переименование исключений



Переименование исключений

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

with Ada.IO_Exceptions;package My_IO is Data_Error: exception renames Ada.IO_Exceptions.Data_Error; . . . end My_IO;



Переименование компонентов



Переименование компонентов

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

with Ada.Text_IO;package Rename_A_Variable is Record_Count: renames Ada.Text_IO.Count; . . . end Rename_A_Variable;



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



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

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

Name : String(0);

Причем, отрезок (0) - это фамилия (last name), отрезок (39) - имя (first name), символ в позиции 60 - это инициал отчества (middle name).Используя переименования мы можем выполнить следующее:

declare Last : String renames Name(0); First : String renames Name(39); Middle : String renames Name(60); begin Ada.Text_IO.Put_Line(Last); Ada.Text_IO.Put_Line(First); Ada.Text_IO.Put_Line(Middle); end;

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



Переименование поля записи



Переименование поля записи

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

subtype Number_Symbol is Character range '0' .. '9'; subtype Address_Character is Character range Ada.Characters.Latin_Space .. Ada.Characters.Latin_LC_Z;type Address_Data is array (Positive range <>) of Address_Character; type Number_Data is array (Positive range <>) of Number_Symbol;type Phone_Number is record Country_Code : Number_Data(); Area_Code : Number_Data(); Prefix : Number_Data(); Last_Four : Number_Data(); end record;type Address_Record is record The_Phone : Phone_Number; Street_Address_1 : Address_Data(0); Street_Address_2 : Address_Data(0); City : Address_Data(5); State : Address_Data(); Zip : Number_Data(); Plus_4 : Number_Data(); end record;One_Address_Record : Address_Record;

Используя переименование, мы можем переименовать один из внутренних компонентов переменной записи One_Address_Record типа Address_Record, для прямого использования в программе.Например, мы можем переименовать Area_Code в инструкции блока:

declare AC: Number_Data renames One_Address_Record.The_Phone.Area_Code; begin . . . end;

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



Переименование знаков операций



Переименование знаков операций

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

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

with Ada.Text_IO;procedure diamond1 is package TIO renames Ada.Text_IO; function "+" (L, R: TIO.Count) return TIO.Count renames TIO."+"; function "-" (L, R: TIO.Count) return TIO.Count renames TIO."-"; . . .

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

package Nested is type T1 is private; type Status is (Off, Low, Medium, Hight); package Operators is function ">=" (L, R: Status) return Boolean renames Nested.">="; function "=" (L, R: Status) return Boolean renames Nested."="; end Operators;private type T1 is ... . . . end Nested;

Показанный выше, вложенный пакет может быть сделан доступным путем указания спецификатора контекста "with Nested;", и последующего спецификатора использования "use Nested.Operators;" следующим образом:

with Nested;procedure Test_Nested is use Nested.Operators; . . . begin . . .

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



Переименования



Переименования

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

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



Переменные окружения программы



Переменные окружения программы

Для поддержки организации взаимодействия с переменными окружения операционной системы, реализация компилятора GNAT содержит дополнительный пакет Ada.Command_Line.Environment спецификация которого имеет следующий вид:

package Ada.Command_Line.Environment is function Environment_Count return Natural; function Environment_Value (Number : in Positive) return String;end Ada.Command_Line.Environment;

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

Перенаправление requeue



Перенаправление requeue



Первая программа



Первая программа

Для того, чтобы дать "почувствовать", что представляет из себя программа написанная на языке Ада рассмотрим простую программу. Традиционно, первая программа - это программа которая выводит на экран приветствие: "Hello World!". Не будем нарушать традицию. Итак, на Аде такая программа будет иметь следующий вид:

with Ada.Text_IO; use Ada.Text_IO;procedure Hello is begin Put_Line("Hello World!"); end Hello;

Давайте детально рассмотрим из каких частей состоит текст этой программы.Строка "procedure Hello is" является заголовком процедуры и она указывает имя нашей процедуры.Далее, между зарезервированными словами begin и end, располагается тело процедуры Hello.В этом примере тело процедуры очень простое и состоит из единственной инструкции "Put_Line("Hello World!");".Эта инструкция осуществляет вывод приветствия на экран, вызывая процедуру Put_Line.Процедура Put_Line располагается в пакете текстового ввода/вывода Ada.Text_IO, и становится доступной благодаря спецификации контекста в инструкциях "with Ada.Text_IO;" и "use Ada.Text_IO;" (спецификация контекста необходима для указания используемых библиотечных модулей). Здесь, спецификатор контекста состоит из двух спецификаторов: спецификатора совместности with и спецификатора использования use. Cпецификатор совместности with указывает компоненты которые будут использоваться в данном компилируемом модуле. Cпецификатор использования use делает имена используемых объектов непосредственно доступными в данном компилируемом модуле.

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

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

Необходимо заметить, что в отличие от языков С/C++, которые имеет функцию main, и языка Паскаль, который имеет program, в Аде, любая процедура без параметров может быть подпрограммой main (другими словами - головной программой). Таким образом, процедура без параметров может быть выбрана как головная программа во время линковки.

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

with Hello; -- указывает на использование показанной ранее -- процедуры Helloprocedure Use_Hello is begin Hello; -- вызов процедуры Hello end Use_Hello;



Подпрограммы



Подпрограммы

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

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



Подпрограммы как библиотечные модули



Подпрограммы как библиотечные модули

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

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

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

Примечание:
В системе компилятора GNAT существует соглашение согласно которому файлы спецификаций имеют расширение ads (ADa Specification), а файлы тел имеют расширение adb (ADa Body).

Файл спецификации процедуры Display_Values (display_values.ads) будет иметь следующий вид:

procedure Display_Values(Number : Integer);

Файл тела процедуры Display_Values (display_values.adb) будет иметь следующий вид:

with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;procedure Display_Values(Number : Integer) is begin Put(Number); New_Line; end Display_Values;

Третий файл - это файл тела процедуры Ive_Got_A_Procedure (ive_got_a_procedure.adb). В этом случае он будет иметь следующий вид:

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

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

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



Поля типа массив



Поля типа массив

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

type Illegal is record Simple_Field_1: Boolean; Simple_Field_2: Integer; Array_Field : array (0) of Float; -- использование -- анонимного массива -- ЗАПРЕЩЕНО!!! end record;type Some_Array is array (0) of Float; -- предварительно описанный -- тип массива type Legal is record Simple_Field_1: Boolean; Simple_Field_2: Integer; Array_Field : Some_Array; -- компонент предварительно -- описанного типа массив end record;

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

type Some_Array is array (Integer range <>) of Float; -- неограниченный -- массив type Some_Record is record Field_1: Boolean; Field_2: Integer; Field_3: Some_Array (0); -- описание компонента записи -- задает ограничение индекса end record;

Здесь, тип Some_Array - это неограниченный массив. Поэтому, при описании поля Field_3 записи Some_Record указывается ограничение значений индекса для массива - (0). После этого, компонент Field_3 становится ограниченным массивом.

Для доступа к индивидуальному компоненту поля Field_3 какой-либо переменной типа Some_Record можно использовать:

. . . Some_Var : Some_Record; . . . Some_Var.Field_3(1) := 1;

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

Some_Var_1 : Some_Record := (False, 0, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); Some_Var_2 : Some_Record := ( Field_1 => False, Field_2 => 0, Field_3 => (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) );Some_Var_3 : Some_Record := ( Field_1 => True, Field_2 => 10, Field_3 => (others => 0) );

Из приведенных примеров видно, что для инициализации простых полей Field_1 и Field_2 записей Some_Var_1, Some_Var_2, Some_Var_3 типа Some_Record используются обычные литералы соответствующего типа, а для инициализации поля Field_3, которое является массивом, используется агрегат массива. Таким образом, для инициализации подобных структур необходимо использовать вложенные агрегаты.



Поля записей типа String



Поля записей типа String

Частным случаем использования массивов в качестве компонентов записей являются строки String. Тип String, по сути, является предопределенным неограниченным массивом символов, поэтому для строк, при описании полей записи, допускается как предварительное описание какого-либо строкового типа или подтипа, так и непосредственное использование типа String. Например:

type Name_String is new String(0); subtype Address_String is String(0);type Person is record First_Name: Name_String; Last_Name : Name_String; Address : Address_String; Phone : String(5); end record;

В приведенном выше примере описания типа записи Person, для описания полей First_Name и Last_Name используется самостоятельный строковый тип Name_String, производный от типа String. Для описания поля Address, используется подтип Address_String. Следует заметить, что тип Name_String и подтип Address_String, оба описывают ограниченные строки (или массивы символов). При описании поля Phone непосредственно использован тип String. В этом случае, для типа String, указывается ограничение для значений индекса - (5).

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

Chief : Person := ( First_Name => "Matroskin ", Last_Name => "Kot ", Address => "Prostokvashino ", Phone => "8-9-222-333 " );



Потоки ввода/вывода



Потоки ввода/вывода

Стандарт Ada95 обогатил средства ввода/вывода Ады возможностью использования гетерогенных (состоящих из различных по составу, свойствам, происхождению частей) потоков ввода/вывода.Основная идея этого подхода заключается в том, что существует поток данных который ассоциируется с каким-либо файлом.За счет использования потоковых механизмов, обработка такого файла может быть выполнена последовательно, подобноAda.Sequential_IO, или позиционно, подобно Ada.Direct_IO.Причем, в отличие традиционных средств файлового ввода вывода которые обеспечиваются пакетами Ada.Sequential_IO и Ada.Direct_IO, один и тот же поток позволяет выполнять чтение/запись для данных различного типа.Для обеспечения поддержки механизмов потокового ввода/вывода Ада предоставляет стандартный пакет Ada.Streams.Stream_IO.

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

Пакет Ada.Streams.Stream_IO предоставляет средства которые позволяют создавать, открывать и закрывать файлы обычным образом.Далее, функция Stream, которая в качестве параметра принимает значение типа File_Type (потоковый файл), позволяет получить доступ к потоку ассоциируемому с этим файлом.Схематически, начало спецификации этого пакета имеет следующий вид:

package Ada.Streams.Stream_IO is type Stream_Access is access all Root_Stream_Type'Class; type File_Type is limited private; -- Create, Open, ... function Stream(File: in File_Type) return Stream_Access; . . . end Ada.Streams.Stream_IO;

Заметим, что все объекты потокового типа являются производными от абстрактного типа Ada.Streams.Root_Stream_Type и обычно получают доступ к потоку через параметр который ссылается на объект типа Ada.Streams.Root_Stream_Type'Class.

Последовательная обработка потоков выполняется с помощью атрибутов 'Read, 'Write, 'Input и 'Output.Эти атрибуты предопределены для каждого нелимитированного типа.Следует заметить, что Ада, с помощью инструкции описания атрибута, предоставляет программисту возможность переопределения этих атрибутов.Таким образом, при необходимости, мы можем переопределять атрибуты которые установлены по умолчанию и описывать атрибуты для лимитированных типов.

Атрибуты T'Read и T'Write принимают параметры которые указывают используемый поток и элемент типа T следующим образом:

procedure T'Write(Stream : access Streams.Root_Stream_Type'Class; Item : in T);procedure T'Read(Stream : access Streams.Root_Stream_Type'Class; Item : out T);

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

type Date is record Day : Integer; Month : Month_Name; Year : Integer; end record;

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

use Streams.Stream_IO; Mixed_File : File_Type; S : Stream_Access; . . . Create(Mixed_File); S := Stream(Mixed_File); . . . Date'Write(S, Some_Date); Integer'Write(S, Some_Integer); Month_Name'Write(S, This_Month); . . .

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

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

procedure Date'Write(Stream : access Streams.Root_Stream_Type'Class; Item : in Date) is begin Integer'Write(Stream, Item.Day); Month_Name'Write(Stream, Item.Month); Integer'Write(Stream, Item.Year); end;

Мы можем написать свою собственную версию для Date'Write.Предположим, что нам необходимо осуществлять вывод имени месяца в виде соответствующего целого значения:

procedure Date_Write(Stream : access Streams.Root_Stream_Type'Class; Item : in Date) is begin Integer'Write(Stream, Item.Day); Integer'Write(Stream, Month_Name'Pos(Item.Month) + 1); Integer'Write(Stream, Item.Year); end Date_Write;for Date'Write use Date_Write;

тогда, следующая инструкция

Date'Write(S, Some_Date);

будет использовать новый формат для вывода значений типа Date.

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

Примечательно, что мы изменили формат вывода Month_Name только для случая Date.Если нам нужно изменить формат вывода Month_Name для всех случаев, то разумнее переопределить Month_Name'Write чем Date'Write.Тогда, это произведет к косвенному изменению формата вывода для типа Date.

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

Ситуация несколько усложняется для массивов и записей с дискриминантами, поскольку необходимо принимать во внимание дополнительную информацию предоставляемую значениями границ массива и значениями дискриминантов.(В случае дискриминанта значение которого равно значению по умолчанию, дискриминант рассматривается как обычный компонент записи).Это выполняется с помощью использования дополнительных атрибутов 'Input и 'Output.Основная идея заключается в том, что атрибуты 'Input и 'Output обрабатывают дополнительную информацию (если она есть) и затем вызывают 'Read и 'Write для обработки остальных значений.Их описание имеет следующий вид:

procedure T'Output(Stream : access Streams.Root_Stream_Type'Class; Item : in T);function T'Input(Stream: access Streams.Root_Stream_Type'Class) return T;

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

Таким образом, в случае массива процедура 'Output выводит значения границ и, после этого, вызывает 'Write непосредственно для самого значения.

В случае типа записи с дискриминантами, если запись имеет дискриминанты значения которых равны значениям по умолчанию, то 'Output просто вызывает 'Write, которая трактует дискриминант как простой компонент записи.Если значение дискриминанта не соответствует тому значению, которое указано как значение по умолчанию, то сначала 'Output выводит дискриминанты записи, а затем вызывает 'Write для обработки остальных компонентов записи.В качестве примера, рассмотрим случай определенного подтипа, чей тип - это первый подтип, который не определен:

subtype String_6 is String(1 .. 6); S: String_6 := "String"; . . . String_6'Output(S); -- выводит значения границ и "String" String_6'Write(S); -- не выводит значения границ

Примечательно, что атрибуты 'Output и 'Write принадлежат типам и, таким образом, не имеет значения или мы записываем String_6'Write, или String'Write.

Приведенное выше описание работы T'Input и T'Output относится к атрибутам которые заданы по умолчанию.Они могут быть переопределены для выполнения чего-либо другого, причем не обязятельно для вызова T'Read и T'Write.Дополнительно отметим, что Input и Output существуют также для определенного подтипа, и их значения просто вызывают Read и Write.

Для взаимодействия с надклассовыми типами предназначены атрибуты T'Class'Output и T'Class'Input.Для вывода значения надклассового типа, сначала производится вывод внешнего представления тэга, после чего с помощю механизма диспетчеризации (перенаправления) вызывается процедура 'Output которая соответствующим образом выводит специфические значения (вызывая 'Write).Подобным образом, для ввода значения такого типа, сначала производится чтение тэга, а затем, в соответствии со значением тэга, с помощю механизма диспетчеризации (перенаправления) вызывается функция Input.Для полноты, также описаны атрибуты T'Class'Write и T'Class'Read которые выполняют диспетчеризацию (перенаправление) вызовов к подпрограммам определяемых атрибутами 'Write и 'Read специфического типа, который идентифицируется тэгом.

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

Теперь можно продолжить рассмотрение структуры которая лежит в основе всех этих механизмов.Все потоки являются производными от абстрактного типа Streams.Root_Stream_Type который имеет две абстрактных операции Read и Write описанные следующим образом:

procedure Read(Stream : in out Root_Stream_Type; Item : out Stream_Element_Array; Last : out Stream_Element_Offset) is abstract;procedure Write(Stream : in out Root_Stream_Type; Item : in Stream_Element_Array) is abstract;

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

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

procedure My_Write(Stream : access Streams.Root_Stream_Type'Class; Item : T) is begin . . . -- преобразование значений в потоковые элементы Streams.Write(Stream.all, ...); -- диспетчеризации (перенаправления) вызова end My_Write;

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



Позиционное сопоставление



Позиционное сопоставление

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

procedure Demo(X : Integer; Y : Integer); -- спецификация процедуры . . .Demo(1, 2);

В этом случае, фактический параметр 1 будет подставлен вместо первого формального параметра X, а фактический параметр 2 будет подставлен вместо второго формального параметра Y.



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



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

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

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

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

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

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

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

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

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



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



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

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

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



Предопределенные исключения



Предопределенные исключения

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

Constraint_Error Ошибка ограничения
Numeric_Error  -  Ошибка числа
Program_Error  -  Ошибка программы
Storage_Error  -  Ошибка памяти
Tasking_Error  -  Ошибка задачи



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



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

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

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



Предопределенный логический тип Boolean



Предопределенный логический тип Boolean

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

type Boolean is (False, True);

Таким образом, переменные логического типа Boolean могут принимать только одно из двух значений: True (истина) или False (ложь).

Примечательно, что предопределеннный логический тип Boolean имеет специальное предназначение. Значения этого типа используются в условных инструкциях языка Ада ("if ... ", "exit when ... ", ...). Это подразумевает, что если вы пытаетесь описать свой собственный логический тип Boolean, и описываете его точно также как и предопределенный тип Boolean (полное имя предопределенного логического типа - Standard.Boolean), то вы получаете абсолютно самостоятельный тип(!). В результате, вы не можете использовать значения, описанного вами типа Boolean, в условных инструкциях языка Ада, которые ожидают только тип Standard.Boolean.

Значения предопределенного логического типа Standard.Boolean возвращают знаки операций сравнения:

= -- равно /= -- не равно < -- меньше <= -- меньше или равно > -- больше >= -- больше или равно

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

and -- логическое И: вычисляются и левая, и правая часть выражения and then -- логическое И: правая часть выражения вычисляется, если -- результат вычисления левой части - Trueor -- логическое ИЛИ: вычисляются и левая, и правая часть выражения or else -- логическое ИЛИ: правая часть выражения вычисляется, если -- результат вычисления левой части - Falsexor -- исключающее ИЛИ not -- отрицание (инверсия); унарная операция

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

(B /= 0) and (A/B > 0)

в случае, когда значение B равно нулю будет возникать ошибка деления на ноль. Причина в том, что значение части выражения, расположенной справа от "and", вычисляется всегда, не зависимо от значения результата полученного при вычислении (B /= 0).

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

(B /= 0) and then (A/B > 0)

В этом случае, обработка выражения справа от "and then" будет производиться только в случае когда B не равно нулю, т.е. результат слева от "and then" - True.

Можно переписать предыдущий пример с использованием "or else". Тогда, обработка логического выражения будет завершена в случае если значение слева от "or else" вычислено как True:

(B = 0) or else (A/B

В заключение обсуждения логического типа отметим, что Ада не позволяет одновременное использование "and" ("and" или "and then"), "or" ("or" или "or else") и "xor" в одном выражении не разделенном скобками. Это уменьшает вероятность разночтения содержимого сложного логического выражения. Например:

(A < B) and (B > C) or (D < E) -- запрещено ((A < B) and (B > C)) or (D < E) -- разрешено



Предопределенный тип Integer



Предопределенный тип Integer

Предопределенный целочисленный тип Integer описан в пакете Standard (пакет Standard не нужно указывать в инструкциях спецификации контекста with и use). Точный диапазон целочисленных значений, предоставляемых этим типом, зависит от конкретной реализации компилятора и/или оборудования. Однако, стандарт определяет минимально допустимый диапазон значений для этого типа от -(2 ** 15) до +(2 ** 15 - 1) (например, в случае 32-битных систем, таких как Windows или Linux, для реализации компилятора GNAT диапазон значений типа Integer будет от -(2 ** 31) до +(2 ** 31 - 1)).

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



Преимущества и недостатки настраиваемых модулей



Преимущества и недостатки настраиваемых модулей

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

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

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







Преобразование численных типов



Преобразование численных типов

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

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

X : Integer := 4; Y : Float;Y := Float(X); . . .X := Integer(Y);

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

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

Значение Float Округленное значение Integer
2
1
- -2
- -1



Прерывания



Прерывания

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

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



Примеры целочисленных описаний



Примеры целочисленных описаний

Ниже приводятся примеры различных целочисленных описаний Ады.

-- описания целочисленных статических переменныхCount : Integer; X, Y, Z : Integer; Amount : Integer := 0;-- описания целочисленных констант (иначе - именованных чисел)Unity : constant Integer := 1; Speed_Of_Light : constant := 300_000; -- тип Universal_Integer A_Month : Integer range 2;-- описания целочисленных типов и подтипов -- ( см. разделы "Подтипы" и "Производные типы" )subtype Months is Integer range 2; -- огранниченный тип Integer -- подтипы - совместимы с их базовым типом (здесь - Integer) -- например, переменная типа Month может быть "смешана" с переменными -- типа Integertype File_Id is new Integer; -- новый целочисленный тип, производный -- от типа Integertype Result_Range is new Integer range 0_000; -- производный тип с объявлением ограниченияtype Other_Result_Range is range 00_000; -- тип производный от Root_Integer -- при этом, компилятор будет выбирать подходящий размер целочисленного значения -- для удовлетворения требований задаваемого диапазона



Примеры организации взаимодействия с C



Примеры организации взаимодействия с C

Простым примером импорта C-функции может служить пример использования функции read системы UNIX в процедуре, написанной на Аде:

procedure Read( File_descriptor : in Integer; Buffer : in out String; No_Bytes : in Integer; No_Read : out Integer) is function Read( File_descriptor : Integer; Buffer : System.Address; No_Bytes : Integer) return Integer; pragma Import(C, read, "read");begin No_Read := Read(File_descriptor, Buffer(Buffer'First)'Address, No_Bytes); end Read;

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

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

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

void something(*int[]);

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

type Vector is array(Natural range <>) of Integer;procedure Something(Item : Vector) is function C_Something(Address : System.Address); pragma Import(C, C_Something, "something");begin if Item'Length = 0 then C_Something(System.Null_Address); else C_Something(Item(Item'First)'Address); end if; end Something;

Рассмотрим более сложный пример, который демонстрирует использование C-функции execv системы UNIX, описанную в C следующим образом:

int execv(const char *path, char *const argv[]);

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

---------------------------------------------------------- type String_Ptr is access all String; type String_Array is array(Natural range <>) of String_Ptr;function execv( Path : String; Arg_List : String_Array) return Interfaces.C.Int;--------------------------------------------------------- -- execv заменяет текущий процесс на новый. -- Список аргументов передается как параметры командной -- строки для вызываемой программы. -- -- Для вызова этой подпрограммы можно: -- -- Option2 : aliased String := "-b"; -- Option3 : aliased String := "-c"; -- Option4 : String := "Cxy"; -- Result : Interfaces.C.Int; -- ... -- Result := execv(Path => "some_program", -- -- построение массива указателей на строки... -- argv => String_Array'(new String'("some_program"), -- new String'("-a"), -- Option2'Unchecked_Access, -- Option3'Unchecked_Access, -- new String'('-' & Option4)); -- -- Допустимо использовать любую комбинацию -- динамически размещаемых строк и 'Unchecked_Access -- к aliased переменным. -- Однако, необходимо отметить, что нельзя выполнить -- "some_String"'Access, поскольку Ада требует имя, -- а не значение, для отрибута 'Access------------------------------------------------------------

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

function execv( Path : String; argv : String_Array ) return Interfaces.C.Int is Package C renames Interfaces.C; Package Strings renames Interfaces.C.Strings; C_Path : constant Strings.Chars_Ptr(.Path'Length + 1) := Strings.New_String(Path); type Char_Star_Array is array(.argv'Length + 1) of Strings.Char_Array_Ptr; C_Argv : Char_Star_Array; Index : Integer; Result : C.int; ------------------------------------------------------------ function C_Execv( Path : Strings.Char_Ptr; C_Arg_List : Strings.Char_Ptr) return C.int; pragma Import(C, C_Execv, "execv"); ------------------------------------------------------------begin -- установка массива указателей на строки Index := 0; for I in argv'Range loop Index := Index + 1; C_Argv(Index) := Strings.New_String(argv(I).all)); end loop; -- добавление C-значения null в конец массива адресов C_Argv(C_Argv'Last) := Strings.Null_Ptr; -- передача адресов первых элементов каждого параметра, -- как это ожидает C Result := C_Execv( C_Path(1)'Address, C_Argv(1)'Address)); -- освобождение памяти, поскольку часто это не выполняется for I in argv'Range loop Strings.Free(argv(I)); end loop; Strings.Free(C_Path); return Result; end execv;

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

Примитивные и не примитивные операции над тэговыми типами Наследование операций



Примитивные и не примитивные операции над тэговыми типами
          Наследование операций

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

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

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

Для уточнения сказанного рассмотрим следующий пример:

package Simple_Objects is type Object_1 is tagged record Field_1 : Integer; end record; -- примитивные операции типа Object_1 procedure Method_1 (Self: in out Object_1); type Object_2 is new Object_1 with record Field_2 : Integer; end record; -- примитивные операции типа Object_2 procedure Method_2 (Self: in out Object_2); procedure Method_2 (Self: in out Object_1); -- НЕДОПУСТИМО!!! -- должна быть примитивной операцией для Object_1, -- но недопустима поскольку следует за описанием типа Object_2 -- который является производным от типа Object_1end Simple_Objects;

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

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

. . . procedure Method_1 (Self: in out Object_2); . . .

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

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

package Simple_Objects is type Object_1 is tagged record Field_1 : Integer; end record; -- примитивные операции типа Object_1 procedure Method_1 (Self: in out Object_1); . . . package Constructors is -- внутренний пакет содержащий не наследуемые -- операции типа Object_1 function Create (Field_1_Value: in Integer) return Object_1; . . . end Constructors; . . .end Simple_Objects;

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

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



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



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

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

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

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

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

Исключение Constraint_Error имеет несколько подавляемых проверок:

pragma Suppress (Access_Check); pragma Suppress (Discriminant_Check); pragma Suppress (Idex_Check); pragma Suppress (Length_Check); pragma Suppress (Range_Check); pragma Suppress (Division_Check); pragma Suppress (Owerflow_Check);

Исключение Program_Error имеет только одну подавляемую проверку:

pragma Suppress (Elaboration_Check);

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

pragma Suppress (Storage_Check);



Принудительное завершение abort



Принудительное завершение abort

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

abort Some_Task_Name;

Здесь Some_Task_Name - это имя какого-либо объекта задачи.Считается, что принудительно прекращенная задача находится в "ненормальном" (abnormal) состоянии и не может взаимодействовать с другими задачами.После того, как состояние задачи отмечено как "ненормальное", выполнение ее тела прекращается.Это подразумевает, что прекращается выполнение любых инструкций, расположенных в теле задачи, за исключением тех, которые вызывают операции, отложенные до принудительного прекращения (abort-deffered operations).

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

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



Приоритеты



Приоритеты

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

pragma Interrupt_Priority ( expression );

Отсутствие выражения expression воспринимается как установка максимального системного приоритета (Interrupt_Priority'Last).Пока обрабатываются операции этого защищенного объекта, прерывания с равным или низшим приоритетом будут заблокированы.Во избежание возникновения ситуации инверсии приоритетов, любая задача, вызывающая операции этого защищенного объекта, должна устанавливать свой приоритет в приоритет этого защищенного объекта на время выполнения операции, отражая срочность завершения операции.Благодаря этому прерывания становятся не блокируемыми.Любой обработчик прерывания обрабатывается с приоритетом своего защищенного объекта, который может быть выше чем приоритет прерывания, если этот же обработчик защищенного объекта обрабатывает более одного вида прерывания.В дополнение к этому, для динамического изменения приоритера, может быть использована процедура Set_Priority, расположенная в пакете Ada.Dynamic_Priorities.