Rationale for Ada 2005: Exceptions, generics etc

RUSTOP
BACKNEXT

ENG

4. Pragmas and Restrictions

@ Ada 2005 introduces a number of new pragmas and Restrictions identifiers. Many of these were described in the previous paper when discussing tasking and the Real-Time and High Integrity annexes. For convenience here is a complete list giving the annex if appropriate.

@ The new pragmas are

@ The new Restrictions identifiers are

@ We will now discuss in detail the pragmas and Restrictions identifiers in the core language and so not discussed in the previous paper.

@ First there is the pragma Assert and the associated pragma Assertion_Policy. Their syntax is as follows

  1        pragma Assert ([Check =>] boolean_expression [, [Message =>] string_expression]);
  2        pragma Assertion_Policy (policy_identifier);

@ The first parameter of Assert is thus a boolean expression and the second (and optional) parameter is a string. Remember that when we write Boolean we mean of the predefined type whereas boolean includes any type derived from Boolean as well.

@ The parameter of Assertion_Policy is an identifier which controls the behaviour of the pragma Assert. Two policies are defined by the language, namely, Check and Ignore. Further policies may be defined by the implementation.

@ There is also a package Ada.Assertions thus

  1        package Ada.Assertions is
  2                pragma Pure (Assertions);
  3                Assertion_Error : exception;
  4                procedure Assert (Check : in Boolean);
  5                procedure Assert (Check : in Boolean; Message : in String);
  6        end Ada.Assertions;

@ The pragma Assert can be used wherever a declaration or statement is allowed. Thus it might occur in a list of declarations such as

  1        N : constant Integer := ... ;
  2        pragma Assert (1 < N);
  3        A : Real_Matrix (1 .. N, 1 .. N);
  4        EV : Real_Vector (1 .. N);

@ and in a sequence of statements such as

  1        pragma Assert (Transpose (A) = A, "A not symmetric");
  2        EV := Eigenvalues (A);

@ If the policy set by Assertion_Policy is Check then the above pragmas are equivalent to

  1        if not 1 < N then
  2                raise Assertion_Error;
  3        end if;

@ and

  1        if not Transpose (A) = A then
  2                raise Assertion_Error with "A not symmetric";
  3        end if;

@ Remember from Section 2 that a raise statement without any explicit message is not the same as one with an explicit null message. In the former case a subsequent call of Exception_Message returns implementation defined information whereas in the latter case it returns a null string. This same behaviour thus occurs with the Assert pragma as well – providing no message is not the same as providing a null message.

@ If the policy set by Assertion_Policy is Ignore then the Assert pragma is ignored at execution time – but of course the syntax of the parameters is checked during compilation.

@ The two procedures Assert in the package Ada.Assertions have an identical effect to the corresponding Assert pragmas except that their behaviour does not depend upon the assertion policy. Thus the call

  1        Assert (Some_Test);

@ is always equivalent to

  1        if not Some_Test then
  2                raise Assertion_Error;
  3        end if;

@ In other words we could define the behaviour of

  1        pragma Assert (Some_Test);

@ as equivalent to

  1        if policy_identifier = Check then
  2                Assert (Some_Test); -- call of procedure Assert
  3        end if;

@ Note again that there are two procedures Assert, one with and one without the message parameter.

@ These correspond to raise statements with and without an explicit message.

@ The pragma Assertion_Policy is a configuration pragma and controls the behaviour of Assert throughout the units to which it applies. It is thus possible for different policies to be in effect in different parts of a partition.

@ An implementation could define other policies such as Assume which might mean that the compiler is free to do optimizations based on the assumption that the boolean expressions are true although there would be no code to check that they were true. Careless use of such a policy could lead to erroneous behaviour.

@ There was some concern that pragmas such as Assert might be misunderstood to imply that static analysis was being carried out. Thus in the SPARK language [2], the annotation --# assert N /= 0 is indeed a static assertion and the appropriate tools can be used to verify this.

@ However, other languages such as Eiffel have used assert in a dynamic manner as now introduced into Ada 2005 and, moreover, many implementations of Ada have already provided a pragma Assert so it is expected that there will be no confusion with its incorporation into the standard.

@ Another pragma with a related flavour is No_Return. This can be applied to a procedure (not to a function) and asserts that the procedure never returns in the normal sense. Control can leave the procedure only by the propagation of an exception or it might loop forever (which is common among certain real-time programs). The syntax is

  1        pragma No_Return (procedure_local_name {, procedure_local_name});

@ Thus we might have a procedure Fatal_Error which outputs some message and then propagates an exception which can be handled in the main subprogram. For example

  1        procedure Fatal_Error (Msg : in String) is
  2                pragma No_Return (Fatal_Error);
  3        begin
  4                Put_Line (Msg);
  5                ... -- other last wishes
  6                raise Death;
  7        end Fatal_Error;
  8        ...
  9        procedure Main is
 10                ...
 11                ...
 12                Put_Line ("Program terminated successfully");
 13        exception
 14                when Death  => Put_Line ("Program terminated : known error");
 15                when others => Put_Line ("Program terminated : unknown error");
 16        end Main;

@ There are two consequences of supplying a pragma No_Return. ? The implementation checks at compile time that the procedure concerned has no explicit return statements. There is also a check at run time that it does not attempt to run into the final end – Program_Error is raised if it does as in the case of running into the end of a function. ? The implementation is able to assume that calls of the procedure do not return and so various optimizations can be made.

@ We might then have a call of Fatal_Error as in

  1        function Pop return Symbol is
  2        begin
  3                if Top = 0 then
  4                        Fatal_Error ("Stack empty"); -- never returns
  5                elsif
  6                        Top := Top1;
  7                        return S (Top+1);
  8                end if;
  9        end Pop;

@ If No_Return applies to Fatal_Error then the compiler should not compile a jump after the call of Fatal_Error and should not produce a warning that control might run into the final end of Pop.

@ The pragma No_Return now applies to the predefined procedure Raise_Exception. To enable this to be possible its behaviour with Null_Id has had to be changed. In Ada 95 writing

  1        Raise_Exception (Null_Id, "Nothing");
  2
  3

@ does nothing at all (and so does return in that case) whereas in Ada 2005 it is defined to raise Constraint_Error and so now never returns.

@ We could restructure the procedure Fatal_Error to use Raise_Exception thus

  1        procedure Fatal_Error (Msg : in String) is
  2                pragma No_Return (Fatal_Error);
  3        begin
  4                ... -- other last wishes
  5                Raise_Exception (Death'Identity, Msg);
  6        end Fatal_Error;

@ Since pragma No_Return applies to Fatal_Error it is important that we also know that Raise_Exception cannot return.

@ The exception handler for Death in the main subprogram can now use Exception_Message to print out the message.

@ Remember also from Section 2 above that we can now also write

  1        raise Death with Msg;
  2        rather than call Raise_Exception.

@ The pragma No_Return is a representation pragma. If a subprogram has no distinct specification then the pragma No_Return is placed inside the body (as shown above). If a subprogram has a distinct specification then the pragma must follow the specification in the same compilation or declarative region. Thus one pragma No_Return could apply to several subprograms declared in the same package specification.

@ It is important that dispatching works correctly with procedures that do not return. A non-returning dispatching procedure can only be overridden by a non-returning procedure and so the overriding procedure must also have pragma No_Return thus

  1        type T is tagged ...
  2        procedure P (X : T; ... );
  3        pragma No_Return (P);
  4        ...
  5        type TT is new T with ...
  6        overriding procedure P (X : TT; ... );
  7        pragma No_Return (P);

@ The reverse is not true of course. A procedure that does return can be overridden by one that does not.

@ It is possible to give a pragma No_Return for an abstract procedure, but obviously not for a null procedure. A pragma No_Return can also be given for a generic procedure. It then applies to all instances.

@ The next new pragma is Preelaborable_Initialization. The syntax is

  1        pragma Preelaborable_Initialization (direct_name);

@ This pragma concerns the categorization of library units and is related to pragmas such as Pure and Preelaborate. It is used with a private type and promises that the full type given by the parameter will indeed have preelaborable initialization. The details of its use will be explained in the next paper.

@ Another new pragma is Unchecked_Union. The syntax is

  1        pragma Unchecked_Union (first_subtype_local_name);

@ The parameter has to denote an unconstrained discriminated record subtype with a variant part. The purpose of the pragma is to permit interfacing to unions in C. The following example was given in the Introduction

  1        type Number (Kind : Precision) is
  2                record
  3                        case Kind is
  4                                when Single_Precision =>
  5                                        SP_Value : Long_Float;
  6                                when Multiple_Precision =>
  7                                        MP_Value_Length : Integer;
  8                                        MP_Value_First  : access Long_Float;
  9                        end case;
 10                end record;
 11        pragma Unchecked_Union (Number);

@ Specifying the pragma Unchecked_Union ensures the following ? The representation of the type does not allow space for any discriminants. ? There is an implicit suppression of Discriminant_Check. ? There is an implicit pragma Convention (C).

@ The above Ada text provides a mapping of the following C union

  1        union
  2        {
  3                double spvalue;
  4                struct
  5                {
  6                        int length;
  7                        double* first;
  8                } mpvalue;
  9        } number;

@ The general idea is that the C programmer has created a type which can be used to represent a floating point number in one of two ways according to the precision required. One way is just as a double length value (a single item) and the other way is as a number of items considered juxtaposed to create a multiple precision value. This latter is represented as a structure consisting of an integer giving the number of items followed by a pointer to the first of them. These two different forms are the two alternatives of the union.

@ In the Ada mapping the choice of precision is governed by the discriminant Kind which is of an enumeration type as follows

  1        type Precision is (Single_Precision, Multiple_Precision);

@ In the single precision case the component SP_Value of type Long_Float maps onto the C component spvalue of type double.

@ The multiple precision case is somewhat troublesome. The Ada component MP_Value_Length maps onto the C component length and the Ada component MP_Value_First of type access Long_Float maps onto the C component first of type double*.

@ In our Ada program we can declare a variable thus

  1        X : Number (Multiple_Precision);

@ and we then obtain a value in X by calling some C subprogram. We can then declare an array and map it onto the C sequence of double length values thus

  1        A : array (1 .. X.MP_Value_Length) of Long_Float;
  2        for A'Address use X.MP_Value_First.all'Address;
  3        pragma Import (C, A);

@ The elements of A are now the required values. Note that we don't use an Ada array in the declaration of Number because there might be problems with dope information.

@ The Ada type can also have a non-variant part preceding the variant part and variant parts can be nested. It may have several discriminants.

@ When an object of an unchecked union type is created, values must be supplied for all its discriminants even though they are not stored. This ensures that appropriate default values can be supplied and that an aggregate contains the correct components. However, since the discriminants are not stored, they cannot be read. So we can write

  1        X : Number := (Single_Precision, 45.6);
  2        Y : Number (Single_Precision);
  3        ...
  4        Y.SP_Value := 55.7;

@ The variable Y is said to have an inferable discriminant whereas X does not. Although it is clear that playing with unchecked unions is potentially dangerous, nevertheless Ada 2005 imposes certain rules that avoid some dangers. One rule is that predefined equality can only be used on operands with inferable discriminants; Program_Error is raised otherwise. So

  1        if Y = 55.8 then -- OK
  2        if X = 45.5 then -- raises Program_Error
  3        if X = Y then -- raises Program_Error

@ It is important to be aware that unchecked union types are introduced in Ada 2005 for the sole purpose of interfacing to C programs and not for living dangerously. Thus consider

  1        type T (Flag : Boolean := False) is
  2                record
  3                        case Flag is
  4                                when False => F1 : Float := 0.0;
  5                                when True  => F2 : Integer := 0;
  6                        end case;
  7                end record;
  8        pragma Unchecked_Union (T);

@ The type T can masquerade as either type Integer or Float. But we should not use unchecked union types as an alternative to unchecked conversion. Thus consider

  1        X : T; -- Float by default
  2        Y : Integer := X.F2; -- erroneous

@ The object X has discriminant False by default and thus has the value zero of type Integer. In the absence of the pragma Unchecked_Union, the attempt to read X.F2 would raise Constraint_Error because of the discriminant check. The use of Unchecked_Union suppresses the discriminant check and so the assignment will occur. But note that the ARM clearly says (11.5(26)) that if a check is suppressed and the corresponding error situation arises then the program is erroneous.

@ However, assigning a Float value to an Integer object using Unchecked_Conversion is not erroneous providing certain conditions hold such as that Float'Size = Integer'Size.

@ The final pragma to be considered is Unsuppress. Its syntax is

  1        pragma Unsuppress (identifier);

@ The identifier is that of a check or perhaps All_Checks. The pragma Unsuppress is essentially the opposite of the existing pragma Suppress and can be used in the same places with similar scoping rules.

@ Remember that pragma Suppress gives an implementation the permission to omit the checks but it does not require that the checks be omitted (they might be done by hardware). The pragma Unsuppress simply revokes this permission. One pragma can override the other in a nested manner.

@ If both are given in the same region then they apply from the point where they are given and the later one thus overrides.

@ A likely scenario would be that Suppress applies to a large region of the program (perhaps all of it) and Unsuppress applies to a smaller region within. The reverse would also be possible but perhaps less likely.

@ Note that Unsuppress does not override the implicit Suppress of Discriminant_Check provided by the pragma Unchecked_Union just discussed.

@ A sensible application of Unsuppress would be in the fixed point operations mentioned in Section 3 thus

  1        function "*" (Left, Right : Frac) return Frac is
  2                pragma Unsuppress (Overflow_Check);
  3        begin
  4                return Standard."*" (Left, Right);
  5        exception
  6                when Constraint_Error =>
  7                        if (0.0 < Left and 0.0 < Right) or (Left < 0.0 and Right < 0.0) then
  8                                return Frac'Last;
  9                        else
 10                                return Frac'First;
 11                        end if;
 12        end "*";

@ The use of Unsuppress ensures that the overflow check is not suppressed even if there is a global Suppress for the whole program (or the user has switched checks off through the compiler command line). So Constraint_Error will be raised as necessary and the code will work correctly.

@ In Ada 95 the pragma Suppress has the syntax

  1        pragma Suppress (identifier [, [On =>] name]); -- Ada 95

@ The second and optional parameter gives the name of the entity to which the permission applies.

@ There was never any clear agreement on what this meant and implementations varied. Accordingly, in Ada 2005 the second parameter is banished to Annex J so that the syntax in the core language is similar to Unsuppress thus

  1        pragma Suppress (identifier); -- Ada 2005

@ For symmetry, Annex J actually allows an obsolete On parameter for Unsuppress. It might seem curious that a feature should be born obsolescent.

@ A number of new Restrictions identifiers are added in Ada 2005. The first is No_Dependence whose syntax is

  1        pragma Restrictions (No_Dependence => name);

@ This indicates that there is no dependence on a library unit with the given name.

@ The name might be that of a predefined unit but it could in fact be any unit. For example, it might be helpful to know that there is no dependence on a particular implementation-defined unit such as a package Superstring thus

  1        pragma Restrictions (No_Dependence => Superstring);

@ Care needs to be taken to spell the name correctly; if we write Supperstring by mistake then the compiler will not be able to help us.

@ The introduction of No_Dependence means that the existing Restrictions identifier No_Asynchronous_Control is moved to Annex J since we can now write

  1        pragma Restrictions (No_Dependence => Ada.Asynchronous_Task_Control);

@ Similarly, the identifiers No_Unchecked_Conversion and No_Unchecked_Deallocation are also moved to Annex J.

@ Note that the identifier No_Dynamic_Attachment which refers to the use of the subprograms in the package Ada.Interrupts cannot be treated in this way because of the child package Ada.Interrupts.Names. No dependence on Ada.Interrupts would exclude the use of the child package Names as well.

@ The restrictions identifier No_Dynamic_Priorities cannot be treated this way either for a rather different reason. In Ada 2005 this identifier is extended so that it also excludes the use of the attribute Priority and this would not be excluded by just saying no dependence on Ada.Dynamic_Priorities.

@ Two further Restrictions identifiers are introduced to encourage portability. We can write

  1        pragma Restrictions (No_Implementation_Pragmas, No_Implementation_Attributes);

@ These do not apply to the whole partition but only to the compilation or environment concerned.

@ This helps us to ensure that implementation dependent areas of a program are identified.

@ The final new restrictions identifier similarly prevents us from inadvertently using features in Annex J thus

  1        pragma Restrictions (No_Obsolescent_Features);

@ Again this does not apply to the whole partition but only to the compilation or environment concerned. (It is of course not itself defined in Annex J.) The reader will recall that in Ada 83 the predefined packages had names such as Text_IO whereas in Ada 95 they are Ada.Text_IO and so on. In order to ease transition from Ada 83, a number of renamings were declared in Annex J such as

  1        with Ada.Text_IO;
  2        package Text_IO renames Ada.Text_IO;

@ A mild problem is that the user could write these renamings anyway and we do not want the No_Obsolescent_Features restriction to prevent this. Moreover, implementations might actually implement the renamings in Annex J by just compiling them and we don't want to force implementations to use some trickery to permit the user to do it but not the implementation.

@ Accordingly, whether the No_Obsolescent_Features restriction applies to these renamings or not is implementation defined.

Rationale for Ada 2005: Exceptions, generics etc

@ENGRUSTOPBACKNEXT

4. Прагмы и Ограничения

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

@ Новые прагмы:

@ Новые идентификаторы Ограничений:

@ Теперь обсудим подробно прагмы и идентификаторы Ограничений базового языка и не обсуждаемые ранее.

@ Сначала рассмотрим прагму Assert и связанную с ней прагму Assertion_Policy. Их синтаксис следующий:

  1        pragma Assert ([Check =>] boolean_expression [, [Message =>] string_expression]);
  2        pragma Assertion_Policy (policy_identifier);

@ Первый параметр прагмы Assert - булево выражение, второй (опциональный) параметр - строка. Здесь возможно любое выражение имеющее своим результатом тип Boolean.

@ Параметр прагмы Assertion_Policy определяет политику прагмы Assert. Возможны два варианта Check и Ignore. Дальнейшая политика может быть определена реализацией.

@ Имеется также пакет Ada.Assertions:

  1        package Ada.Assertions is
  2                pragma Pure (Assertions);
  3                Assertion_Error : exception;
  4                procedure Assert (Check : in Boolean);
  5                procedure Assert (Check : in Boolean; Message : in String);
  6        end Ada.Assertions;

@ Прагма Assert может использоваться везде, где объявления или операторы разрешены. Таким образом, она может использоваться в списке объявлений:

  1        N : constant Integer := ... ;
  2        pragma Assert (1 < N);
  3        A : Real_Matrix (1 .. N, 1 .. N);
  4        EV : Real_Vector (1 .. N);

@ и в последовательности операторов:

  1        pragma Assert (Transpose (A) = A, "A not symmetric");
  2        EV := Eigenvalues (A);

@ Если политика Assertion_Policy установлена в Check тогда вышеупомянутые прагмы эквивалентны следующим проверкам:

  1        if not 1 < N then
  2                raise Assertion_Error;
  3        end if;

@ и

  1        if not Transpose (A) = A then
  2                raise Assertion_Error with "A not symmetric";
  3        end if;

@ Напомним, что raise оператор без явного сообщения не одно и то же что оператор с явным нулевым сообщением. В первом случае запрос выдаёт Exception_Message, тогда как во втором случае выдаётся пустая строка. Такое же самое поведение происходит и с прагмой Assert - отсутствие сообщения не одно и тоже что пустое сообщение.

@ Если политика в Assertion_Policy установлена в Ignore тогда прагма Assert игнорируется во время выполнения - но конечно синтаксис параметров проверяется во время трансляции.

@ Две процедуры Assert в пакете Ada.Assertions имеют аналогичный прагме Assert эффект за исключением того, что их поведение не зависит от политики Assertions_Policy. Таким образом запрос

  1        Assert (Some_Test);

@ всегда эквивалентен

  1        if not Some_Test then
  2                raise Assertion_Error;
  3        end if;

@ Другими словами, мы могли определить поведение

  1        pragma Assert (Some_Test);

@ как эквивалент

  1        if policy_identifier = Check then
  2                Assert (Some_Test); -- call of procedure Assert
  3        end if;

@ Обратите внимание, что есть две процедуры Assert, одна с и одна без сообщения.

@ Они поддерживают raise операторы с и без явного сообщения.

@ Прагма конфигурации Assertion_Policy управляет поведением Assert в каждом модуле в которым она применяется. Это позволяет определять различную политику в различных частях программы.

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

@ Было некоторое беспокойство, что прагмы такие как Assert могли бы быть неправильно поняты, ибо мргли заставить подразумевать, что выполняется статический анализ. Так на языке SPARK [2], выражение - # assert N /= 0 является действительно статическим утверждением, и соответствующие инструментальные средства могут использоваться, чтобы проверить это.

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

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

  1        pragma No_Return (procedure_local_name {, procedure_local_name});

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

  1        procedure Fatal_Error (Msg : in String) is
  2                pragma No_Return (Fatal_Error);
  3        begin
  4                Put_Line (Msg);
  5                ... -- other last wishes
  6                raise Death;
  7        end Fatal_Error;
  8        ...
  9        procedure Main is
 10                ...
 11                ...
 12                Put_Line ("Program terminated successfully");
 13        exception
 14                when Death  => Put_Line ("Program terminated : known error");
 15                when others => Put_Line ("Program terminated : unknown error");
 16        end Main;

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

@ У нас мог бы быть вызов Fatal_Error как в

  1        function Pop return Symbol is
  2        begin
  3                if Top = 0 then
  4                        Fatal_Error ("Stack empty"); -- never returns
  5                elsif
  6                        Top := Top1;
  7                        return S (Top+1);
  8                end if;
  9        end Pop;

@ Если прагма No_Return относится к Fatal_Error, тогда компилятор не должен предусматривать действия после вызова Fatal_Error и не должен выдавать предупреждение в случае непредвиденного конца Pop без положенного в таких случаях оператора return.

@ Прагма No_Return теперь относится к предопределенной процедуре Raise_Exception. Чтобы сделать это возможным, его поведение с Null_Id должно было быть изменено. На Аде 95 мы пишем:

  1        Raise_Exception (Null_Id, "Nothing");

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

@ Мы могли переделать процедуру Fatal_Error чтобы использовать Raise_Exception таким образом:

  1        procedure Fatal_Error (Msg : in String) is
  2                pragma No_Return (Fatal_Error);
  3        begin
  4                ... -- other last wishes
  5                Raise_Exception (Death'Identity, Msg);
  6        end Fatal_Error;

@ Т.к. прагма No_Return относится к Fatal_Error мы также знаем, что Raise_Exception также не может возвратиться.

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

@ Напомним, что мы можем теперь также написать:

  1        raise Death with Msg;

@ вместо того, чтобы называть Raise_Exception.

@ Прагма No_Return является прагмой представления. Если у подпрограммы нет никакой отличной спецификации тогда прагма No_Return помещается в теле (как показано выше). Если у подпрограммы есть отличная спецификация тогда, прагма должна следовать за спецификацией в той же самой трансляции или декларативной области. Таким образом, одна прагма No_Return может относиться к нескольким подпрограммам, объявленным в той же самой спецификации пакета.

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

  1        type T is tagged ...
  2        procedure P (X : T; ... );
  3        pragma No_Return (P);
  4        ...
  5        type TT is new T with ...
  6        overriding procedure P (X : TT; ... );
  7        pragma No_Return (P);

@ Обратное, естественно, не верно. Процедура, которая действительно возвращается, может быть отменена (overriden) процедурой, который не возвращается.

@ Возможно дать прагму No_Return для абстрактной процедуры, но очевидно не для нулевой процедуры. Прагма No_Return может также быть дана для настраиваемой (generic) процедуры. Она тогда применяется ко всем её экземплярам.

@ Следующая новая прагма - Preelaborable_Initialization.

  1        pragma Preelaborable_Initialization (direct_name);

@ Эта прагма касается классификации библиотечных модулей и связана с такими прагмами как Pure и Preelaborate. Она используется с приватным типом и обещает, что у полного типа, указанного параметром, действительно будет preelaborable инициализация. Подробности её использования будут объяснены в следующей статье.

@ Другая новая прагма - Unchecked_Union.

  1        pragma Unchecked_Union (first_subtype_local_name);

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

  1        type Number (Kind : Precision) is
  2                record
  3                        case Kind is
  4                                when Single_Precision =>
  5                                        SP_Value : Long_Float;
  6                                when Multiple_Precision =>
  7                                        MP_Value_Length : Integer;
  8                                        MP_Value_First  : access Long_Float;
  9                        end case;
 10                end record;
 11        pragma Unchecked_Union (Number);

@ Определение прагмы Unchecked_Union гарантирует что: 1) Представление типа не выделяет пространство для каких бы то ни было дискриминантов. 2) Не осуществляется проверка Discriminant_Check. 3) Есть неявное назначение прагмы Convention (C).

@ Вышеупомянутый текст на Аде обеспечивает отображение следующего объединения на C:

  1        union
  2        {
  3                double spvalue;
  4                struct
  5                {
  6                        int length;
  7                        double* first;
  8                } mpvalue;
  9        } number;

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

@ На Аде выбор точности управляется дискриминантным типом Kind, который имеет перечислимый тип:

  1        type Precision is (Single_Precision, Multiple_Precision);

@ В случае единственной точности компонент SP_Value типа Long_Float накладывается на C компонент spvalue типа double.

@ Случай множественной точности более замысловат. Адовский компонент MP_Value_Length накладывается на C компонент length а адовский компонент MP_Value_First ссылочного типа на тип Long_Float накладывается на C компонент типа double*.

@ В нашей Ада программе мы можем объявить переменную:

  1        X : Number (Multiple_Precision);

@ пусть мы получаем значение X вызывая некоторую C подпрограмму. В этом случае мы можем объявить массив и отобразить его на последовательность C значений типа double таким образом:

  1        A : array (1 .. X.MP_Value_Length) of Long_Float;
  2        for A'Address use X.MP_Value_First.all'Address;
  3        pragma Import (C, A);

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

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

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

  1        X : Number := (Single_Precision, 45.6);
  2        Y : Number (Single_Precision);
  3        ...
  4        Y.SP_Value := 55.7;

@ У переменной Y, как говорят, есть выводимый дискриминант, тогда как у X нет. Понятно, что игра с объединениями без контроля типов потенциально опасна, однако Ада 2005 вволит определенные правила, которые позволяют избежать некоторых опасностей. Первое правило состоит в том, что предопределенное равенство может использоваться только с операндами с выводимыми дискриминантами; в противном случае возбуждается исключение Program_Error. Так:

  1        if Y = 55.8 then -- OK
  2        if X = 45.5 then -- raises Program_Error
  3        if X = Y then -- raises Program_Error

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

  1        type T (Flag : Boolean := False) is
  2                record
  3                        case Flag is
  4                                when False => F1 : Float := 0.0;
  5                                when True  => F2 : Integer := 0;
  6                        end case;
  7                end record;
  8        pragma Unchecked_Union (T);

@ Тип T может рассматриваться как Integer или как Float. Но мы не должны использовать типы объединений без контроля как альтернативу преобразованию без контроля типов. Рассмотрим:

  1        X : T; -- Float by default
  2        Y : Integer := X.F2; -- erroneous

@ Объект X имеет дискриминантное значение False по умолчанию и, таким образом, имеет нулевое значение типа Integer. В отсутствии прагмы Unchecked_Union попытка читать X.F2 вызовет исключение Constraint_Error из-за дискриминантной проверки. Использование прагмы Unchecked_Union подавляет дискриминантную проверку и, таким образом, присваивание произойдет вполне корректно. Но отметим, что ARM ясно говорит (11.5 (26)), что, если проверка подавляется и соответствующая ошибочная ситуация возникает тогда, программа ошибочна.

@ Однако, присваивание значения типа Float объекту типа Integer, используя Unchecked_Conversion не является ошибочными если не нарушается условие Float'Size = Integer'Size.

@ Последняя прагма, которую мы рассмотрим - Unsuppress:

  1        pragma Unsuppress (identifier);

@ Здесь identifier - идентификатор для проверки или возможно All_Checks. Прагма Unsupress, по существу, противоположность существующей прагмы Suppress и может использоваться в тех же самых местах с аналогичными правилами обзора данных.

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

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

@ Наиболее вероятный сценарий их применеия такой, Suppress относится к большой области программы (возможно ко всей), а Unsuppress относится к меньшей области в некоторых пределах. Обратное тоже возможно, но гораздо менее вероятно.

@ Отметим, что Unsuppress, не отменяет неявное подавление проверки Discriminant_Check, предоставленное прагмой Unchecked_Union обсуждаемой выше.

@ Очевидное приложение Unsuppress было бы в операциях с фиксированной точкой, упомянутых в Секции 3 таким образом:

  1        function "*" (Left, Right : Frac) return Frac is
  2                pragma Unsuppress (Overflow_Check);
  3        begin
  4                return Standard."*" (Left, Right);
  5        exception
  6                when Constraint_Error =>
  7                        if (0.0 < Left and 0.0 < Right) or (Left < 0.0 and Right < 0.0) then
  8                                return Frac'Last;
  9                        else
 10                                return Frac'First;
 11                        end if;
 12        end "*";

@ Использование Unsuppress гарантирует, что проверка переполнения не подавляется, даже если есть глобальный Suppress для всей программы (или пользователь её выключил через командную строку компилятора). Таким образом, исключение Constraint_Error будет происходить там где и положено, и код будет работать правильно.

@ В Аде 95 прагма Suppress имеет синтаксис:

  1        pragma Suppress (identifier [, [On =>] name]); -- Ada 95

@ Второй и дополнительный параметр дает имя объекта, к которому применяется разрешение.

@ Никаких ясных соглашений на этот счёт не имелось и варьировалось в зависимости от реализации. Соответственно в Аде 2005 второй параметр выслан в Приложение J как анахронизм, и синтаксис в базовом языке теперь:

  1        pragma Suppress (identifier); -- Ada 2005

@ Для симметрии Приложение J разрешает устаревший параметр On для Unsuppress. Может показаться любопытным, что эта особенность должна родиться уже устаревшей.

@ Много новых идентификаторов Ограничений добавлены в Аде 2005. Один из них - No_Dependence:

  1        pragma Restrictions (No_Dependence => name);

@ Он указывает, что нет никакой зависимости от библиотечного модуля с указанным именем.

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

  1        pragma Restrictions (No_Dependence => Superstring);

@ Необходимо быть внимательным и записать по буквам название правильно; если мы напишем Supperstring по ошибке тогда, то компилятор не будет в состоянии помочь нам.

@ Введение No_Dependence означает, что существующий идентификатор Ограничений No_Asynchronous_Control перемещен в Приложение J, так как мы можем теперь написать:

  1        pragma Restrictions (No_Dependence => Ada.Asynchronous_Task_Control);

@ Точно так же идентификаторы No_Unchecked_Conversion и No_Unchecked_Deallocation также перемещены в Приложение J.

@ Отметим, что идентификатор No_Dynamic_Attachment, который обращается к использованию подпрограмм в пакете Ada.Interrupts не может быть обработан таким образом из-за дочернего пакета Ada.Interrups.Names. Отключение зависимости от Ada.Interrupts исключили бы использование дочернего пакета Name также.

@ Идентификатор ограничений No_Dynamic_Priorities не может быть обработан таким образом по другой причине. В Аде 2005 расширен этот идентификатор так, чтобы это также исключило использование атрибута Priority, и это не было бы исключено, только не говоря зависимости от Ada.Dynamic_Priorities.

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

  1        pragma Restrictions (No_Implementation_Pragmas, No_Implementation_Attributes);

@ Они не относятся к целому разделению, но только к трансляции или соответствующей среде.

@ Это помогает нам гарантировать, что реализация зависящие от выполнения области программы.

@ Последний новый идентификатор ограничений так же предотвращает нас от небрежного использования особенностей Приложения J таким образом:

  1        pragma Restrictions (No_Obsolescent_Features);

@ Снова это не относится к целому разделению, но только к трансляции или соответствующей среде. (Это конечно не непосредственно определено в Приложении J.), читатель напомнит, что в Аде 83 у предопределенных пакетов были названия, такие как Text_IO, тогда как в Аде 95 они - Ада.Text_IO и так далее. Чтобы ослабить переход из Ады 83, многие переименования были объявлены в Приложении J таким образом:

  1        with Ada.Text_IO;
  2        package Text_IO renames Ada.Text_IO;

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

@ Соответственно, ограничение No_Obsolescent_Features относится к этими переименованиями или не является определенной реализацией.

@ ENG RUS

TOP BACK NEXT

2010-10-24 00:26:56

. .