C.3. Controlling the Elaboration Order in Ada 95

In the previous section we discussed the rules in Ada 95 which ensure that Program_Error is raised if an incorrect elaboration order is chosen. However, this is not sufficient. Although we certainly prefer an exception to getting the wrong results, we need ways of avoiding the exception. To achieve this, Ada 95 provides a number of features for controlling the order of elaboration, and we discuss these features in this section.

First, there are several ways of indicating to the compiler that a given unit has no elaboration problems:

packages that do not require a body

In Ada 95, a library package that does not require a body does not permit a body. This means that if we have a such a package, as in:


package Definitions is
   generic
      type m is new integer;
   package Subp is
      type a is array (1 .. 10) of m;
      type b is array (1 .. 20) of m;
   end mm;
end x;

A package that with's Definitions may safely instantiate Definitions.Subp because the compiler can determine that there definitely is no package body to worry about in this case

pragma Pure

Places sufficient restrictions on a unit so that it is impossible for any call to any subprogram in the unit to result in an elaboration problem. This means that the compiler does not need to worry about the order of elaboration for such units, and in particular, does not need to check any calls to any subprograms in this unit.

pragma Preelaborate

This pragma places slightly less fierce restrictions on a unit, but the restrictions are still sufficient to ensure that there are no elaboration problems with any calls to the unit.

pragma Elaborate_Body

This pragma requires that the body of a unit be elaborated immediately after its spec. Suppose a unit A has such a pragma, and unit B does a with of unit A. Now the standard rules require the spec of unit A to be elaborated before the with'ing unit, and given the pragma in A, we also know that the body of A will be elaborated before B, so calls to A are safe and do not need a check.

Note that, unlike pragma Pure and pragma Preelaborate, the use of Elaborate_Body does not guarantee that the program is free of elaboration problems, because it may not be possible to satisfy the requested elaboration order. Let's go back to the example with Unit_1 and Unit_2. If a programmer marks Unit_1 as Elaborate_Body, and not Unit_2, then the order of elaboration will be:


Spec of Unit_2
Spec of Unit_1
Body of Unit_1
Body of Unit_2

Now that means that the call to Func_1 in Unit_2 need not be checked, it must be safe. But the call to Func_2 in Unit_1 may still fail if Expression_1 is equal to 1, and the programmer must still take responsibility for this not being the case.

If all units have pragma Elaborate_Body, then all problems are eliminated, except for calls entirely within a body, which are in any case fully under programmer control. However, this is not always possible. In particular, for our Unit_1/Unit_2 example, if we marked both of them as having pragma Elaborate_Body, then clearly no elaboration order is possible.

The above pragmas allow a server to guarantee safe use by clients, and clearly this is the preferable approach. Consequently a good rule in Ada 95 is to mark units as Pure or Preelaborate if possible, and if this is not possible, mark them as Elaborate_Body if possible. But, as we have discussed, it is not always possible to use one of these three pragmas. So we also provide methods for clients to control the order of elaboration:

pragma Elaborate (unit)

This pragma is placed in the context clause, after a with statement, and it requires that the body of the named unit be elaborated before the unit in which the pragma occurs. The idea is to use this pragma if you know that you will be making calls, directly or indirectly, at elaboration time to subprograms in a given unit.

pragma Elaborate_All (unit)

This is a stronger version of the Elaborate pragma. Consider the following example:


Unit A with's unit B and calls B.Func in elaboration code
Unit B with's unit C, and B.Func calls C.Func

Now if we put a pragma Elaborate (B) in unit A, this ensures that the body of B is elaborated before the call, but not the body of C, so the call to C.Func could still cause Program_Error to be raised.

But the effect of a pragma Elaborate_All is stronger, it requires not only that the body of the named unit be elaborated before the unit doing the with, but also the bodies of all units that the named unit uses, following with links transitively. For example, if we put a pragma Elaborate_All (B) in unit A, then it requires not only that the body of B be elaborated before A, but also the body of C, because B with's C.

We are now in a position to give a usage rule in Ada 95 for avoiding elaboration problems, at least if dynamic dispatching and access to procedure values are not used. We will handle these cases separately later.

The rule is simple. If a unit has elaboration code that can directly or indirectly make a call to a subprogram in a with'ed unit, or instantiate a generic unit in a with'ed unit, then if the with'ed unit does not have pragma Pure, Preelaborate, or Elaborate_Body, then the client should have an Elaborate_All for the with'ed unit. By following this rule a client is assured that calls can be made without risk of an exception. If this rule is not followed, then a program may be in one of four states:

No order exists

No order of elaboration exists which follows the rules, taking into account any Elaborate, Elaborate_All, or Elaborate_Body pragmas. In this case, an Ada 95 compiler must diagnose the situation at bind time, and refuse to build an executable program.

One or more orders exist, all wrong

One or more acceptable elaboration orders exists, and all of them generate an elaboration order problem. In this case, the binder can build an executable program, but Program_Error will be raised when the program is run.

Several orders exist, some right, some wrong

One or more acceptable elaboration orders exists, and some of them work, and some do not. The programmer has not controlled the order of elaboration, so the binder may or may not pick one of the correct orders, and the program may or may not raise an exception when it is run. This is the worst case, because it means that the program may fail when moved to another compiler, or even another version of the same compiler.

One or more orders exists, all right

One ore more acceptable elaboration orders exists, and all of them work. In this case the program runs successfully. This state of affairs can be guaranteed by following the rule we gave above, but may be true even if the rule is not followed.

Note that one additional advantage of following our Elaborate_All rule is that the program continues to stay in the ideal (all orders OK) state even if maintenance changes some bodies of some subprograms. Even if a program that does not follow this rule happens to be safe, this state of affairs may deteriorate silently as a result of maintenance changes.