9.15 Address Clauses

The reference manual allows a general restriction on representation clauses, as found in RM 13.1(22):

“An implementation need not support representation items containing nonstatic expressions, except that an implementation should support a representation item for a given entity if each nonstatic expression in the representation item is a name that statically denotes a constant declared before the entity.”

In practice this is applicable only to address clauses, since this is the only case in which a nonstatic expression is permitted by the syntax. As the AARM notes in sections 13.1 (22.a-22.h):

22.a Reason: This is to avoid the following sort of thing:

22.b X : Integer := F(…); Y : Address := G(…); for X’Address use Y;

22.c In the above, we have to evaluate the initialization expression for X before we know where to put the result. This seems like an unreasonable implementation burden.

22.d The above code should instead be written like this:

22.e Y : constant Address := G(…); X : Integer := F(…); for X’Address use Y;

22.f This allows the expression ‘Y’ to be safely evaluated before X is created.

22.g The constant could be a formal parameter of mode in.

22.h An implementation can support other nonstatic expressions if it wants to. Expressions of type Address are hardly ever static, but their value might be known at compile time anyway in many cases.

GNAT does indeed permit many additional cases of nonstatic expressions. In particular, if the type involved is elementary there are no restrictions (since in this case, holding a temporary copy of the initialization value, if one is present, is inexpensive). In addition, if there is no implicit or explicit initialization, then there are no restrictions. GNAT will reject only the case where all three of these conditions hold:

As noted above in section 22.h, address values are typically nonstatic. In particular the To_Address function, even if applied to a literal value, is a nonstatic function call. To avoid this minor annoyance, GNAT provides the implementation defined attribute ‘To_Address. The following two expressions have identical values:

To_Address (16#1234_0000#)
System'To_Address (16#1234_0000#);

except that the second form is considered to be a static expression, and thus when used as an address clause value is always permitted.

Additionally, GNAT treats as static an address clause that is an unchecked_conversion of a static integer value. This simplifies the porting of legacy code, and provides a portable equivalent to the GNAT attribute To_Address.

Another issue with address clauses is the interaction with alignment requirements. When an address clause is given for an object, the address value must be consistent with the alignment of the object (which is usually the same as the alignment of the type of the object). If an address clause is given that specifies an inappropriately aligned address value, then the program execution is erroneous.

Since this source of erroneous behavior can have unfortunate effects on machines with strict alignment requirements, GNAT checks (at compile time if possible, generating a warning, or at execution time with a run-time check) that the alignment is appropriate. If the run-time check fails, then Program_Error is raised. This run-time check is suppressed if range checks are suppressed, or if the special GNAT check Alignment_Check is suppressed, or if pragma Restrictions (No_Elaboration_Code) is in effect. It is also suppressed by default on non-strict alignment machines (such as the x86).

In some cases, GNAT does not support an address specification (using either form of aspect specification syntax) for the declaration of an object that has an indefinite nominal subtype. An object declaration has an indefinite nominal subtype if it takes its bounds (for an array type), discriminant values (for a discriminated type whose discriminants lack defaults), or tag (for a class-wide type) from its initial value, as in

X : String := Some_Function_Call;
-- String has no constraint, so bounds for X come from function call

This restriction does not apply if the size of the object’s initial value is known at compile time and the type of the object is not class-wide.

An address clause cannot be given for an exported object. More understandably the real restriction is that objects with an address clause cannot be exported. This is because such variables are not defined by the Ada program, so there is no external object to export.

It is permissible to give an address clause and a pragma Import for the same object. In this case, the variable is not really defined by the Ada program, so there is no external symbol to be linked. The link name and the external name are ignored in this case. The reason that we allow this combination is that it provides a useful idiom to avoid unwanted initializations on objects with address clauses.

When an address clause is given for an object that has implicit or explicit initialization, then by default initialization takes place. This means that the effect of the object declaration is to overwrite the memory at the specified address. This is almost always not what the programmer wants, so GNAT will output a warning:

with System;
package G is
   type R is record
      M : Integer := 0;
   end record;

   Ext : R;
   for Ext'Address use System'To_Address (16#1234_1234#);
       |
>>> warning: implicit initialization of "Ext" may
    modify overlaid storage
>>> warning: use pragma Import for "Ext" to suppress
    initialization (RM B(24))

end G;

As indicated by the warning message, the solution is to use a (dummy) pragma Import to suppress this initialization. The pragma tell the compiler that the object is declared and initialized elsewhere. The following package compiles without warnings (and the initialization is suppressed):

with System;
package G is
   type R is record
      M : Integer := 0;
   end record;

   Ext : R;
   for Ext'Address use System'To_Address (16#1234_1234#);
   pragma Import (Ada, Ext);
end G;

A final issue with address clauses involves their use for overlaying variables, as in the following example:

A : Integer;
B : Integer;
for B'Address use A'Address;

or alternatively, using the form recommended by the RM:

A    : Integer;
Addr : constant Address := A'Address;
B    : Integer;
for B'Address use Addr;

In both of these cases, A and B become aliased to one another via the address clause. This use of address clauses to overlay variables, achieving an effect similar to unchecked conversion was erroneous in Ada 83, but in Ada 95 and Ada 2005 the effect is implementation defined. Furthermore, the Ada RM specifically recommends that in a situation like this, B should be subject to the following implementation advice (RM 13.3(19)):

“19 If the Address of an object is specified, or it is imported or exported, then the implementation should not perform optimizations based on assumptions of no aliases.”

GNAT follows this recommendation, and goes further by also applying this recommendation to the overlaid variable (A in the above example) in this case. This means that the overlay works “as expected”, in that a modification to one of the variables will affect the value of the other.

More generally, GNAT interprets this recommendation conservatively for address clauses: in the cases other than overlays, it considers that the object is effectively subject to pragma Volatile and implements the associated semantics.

Note that when address clause overlays are used in this way, there is an issue of unintentional initialization, as shown by this example:

package Overwrite_Record is
   type R is record
      A : Character := 'C';
      B : Character := 'A';
   end record;
   X : Short_Integer := 3;
   Y : R;
   for Y'Address use X'Address;
       |
>>> warning: default initialization of "Y" may
    modify "X", use pragma Import for "Y" to
    suppress initialization (RM B.1(24))

end Overwrite_Record;

Here the default initialization of Y will clobber the value of X, which justifies the warning. The warning notes that this effect can be eliminated by adding a pragma Import which suppresses the initialization:

package Overwrite_Record is
   type R is record
      A : Character := 'C';
      B : Character := 'A';
   end record;
   X : Short_Integer := 3;
   Y : R;
   for Y'Address use X'Address;
   pragma Import (Ada, Y);
end Overwrite_Record;

Note that the use of pragma Initialize_Scalars may cause variables to be initialized when they would not otherwise have been in the absence of the use of this pragma. This may cause an overlay to have this unintended clobbering effect. The compiler avoids this for scalar types, but not for composite objects (where in general the effect of Initialize_Scalars is part of the initialization routine for the composite object):

pragma Initialize_Scalars;
with Ada.Text_IO;  use Ada.Text_IO;
procedure Overwrite_Array is
   type Arr is array (1 .. 5) of Integer;
   X : Arr := (others => 1);
   A : Arr;
   for A'Address use X'Address;
       |
>>> warning: default initialization of "A" may
    modify "X", use pragma Import for "A" to
    suppress initialization (RM B.1(24))

begin
   if X /= Arr'(others => 1) then
      Put_Line ("X was clobbered");
   else
      Put_Line ("X was not clobbered");
   end if;
end Overwrite_Array;

The above program generates the warning as shown, and at execution time, prints X was clobbered. If the pragma Import is added as suggested:

pragma Initialize_Scalars;
with Ada.Text_IO;  use Ada.Text_IO;
procedure Overwrite_Array is
   type Arr is array (1 .. 5) of Integer;
   X : Arr := (others => 1);
   A : Arr;
   for A'Address use X'Address;
   pragma Import (Ada, A);
begin
   if X /= Arr'(others => 1) then
      Put_Line ("X was clobbered");
   else
      Put_Line ("X was not clobbered");
   end if;
end Overwrite_Array;

then the program compiles without the warning and when run will generate the output X was not clobbered.