9. Representation Clauses and Pragmas

This section describes the representation clauses accepted by GNAT, and their effect on the representation of corresponding data objects.

GNAT fully implements Annex C (Systems Programming). This means that all the implementation advice sections in chapter 13 are fully implemented. However, these sections only require a minimal level of support for representation clauses. GNAT provides much more extensive capabilities, and this section describes the additional capabilities provided.

9.1. Alignment Clauses

GNAT requires that all alignment clauses specify 0 or a power of 2, and all default alignments are always a power of 2. Specifying 0 is the same as specifying 1.

The default alignment values are as follows:

  • Elementary Types.

    For elementary types, the alignment is the minimum of the actual size of objects of the type divided by Storage_Unit, and the maximum alignment supported by the target. (This maximum alignment is given by the GNAT-specific attribute Standard'Maximum_Alignment; see Attribute Maximum_Alignment.)

    For example, for type Long_Float, the object size is 8 bytes, and the default alignment will be 8 on any target that supports alignments this large, but on some targets, the maximum alignment may be smaller than 8, in which case objects of type Long_Float will be maximally aligned.

  • Arrays.

    For arrays, the alignment is equal to the alignment of the component type for the normal case where no packing or component size is given. If the array is packed, and the packing is effective (see separate section on packed arrays), then the alignment will be either 4, 2, or 1 for long packed arrays or arrays whose length is not known at compile time, depending on whether the component size is divisible by 4, 2, or is odd. For short packed arrays, which are handled internally as modular types, the alignment will be as described for elementary types, e.g. a packed array of length 31 bits will have an object size of four bytes, and an alignment of 4.

  • Records.

    For the normal unpacked case, the alignment of a record is equal to the maximum alignment of any of its components. For tagged records, this includes the implicit access type used for the tag. If a pragma Pack is used and all components are packable (see separate section on pragma Pack), then the resulting alignment is 1, unless the layout of the record makes it profitable to increase it.

    A special case is when:

    • the size of the record is given explicitly, or a full record representation clause is given, and

    • the size of the record is 2, 4, or 8 bytes.

    In this case, an alignment is chosen to match the size of the record. For example, if we have:

    type Small is record
       A, B : Character;
    end record;
    for Small'Size use 16;
    

    then the default alignment of the record type Small is 2, not 1. This leads to more efficient code when the record is treated as a unit, and also allows the type to specified as Atomic on architectures requiring strict alignment.

An alignment clause may specify a larger alignment than the default value up to some maximum value dependent on the target (obtainable by using the attribute reference Standard'Maximum_Alignment). It may also specify a smaller alignment than the default value for enumeration, integer and fixed point types, as well as for record types, for example

type V is record
   A : Integer;
end record;

for V'alignment use 1;

The default alignment for the type V is 4, as a result of the Integer field in the record, but it is permissible, as shown, to override the default alignment of the record with a smaller value.

Note that according to the Ada standard, an alignment clause applies only to the first named subtype. If additional subtypes are declared, then the compiler is allowed to choose any alignment it likes, and there is no way to control this choice. Consider:

type R is range 1 .. 10_000;
for R'Alignment use 1;
subtype RS is R range 1 .. 1000;

The alignment clause specifies an alignment of 1 for the first named subtype R but this does not necessarily apply to RS. When writing portable Ada code, you should avoid writing code that explicitly or implicitly relies on the alignment of such subtypes.

For the GNAT compiler, if an explicit alignment clause is given, this value is also used for any subsequent subtypes. So for GNAT, in the above example, you can count on the alignment of RS being 1. But this assumption is non-portable, and other compilers may choose different alignments for the subtype RS.

9.2. Size Clauses

The default size for a type T is obtainable through the language-defined attribute T'Size and also through the equivalent GNAT-defined attribute T'Value_Size. For objects of type T, GNAT will generally increase the type size so that the object size (obtainable through the GNAT-defined attribute T'Object_Size) is a multiple of T'Alignment * Storage_Unit.

For example:

type Smallint is range 1 .. 6;

type Rec is record
   Y1 : integer;
   Y2 : boolean;
end record;

In this example, Smallint'Size = Smallint'Value_Size = 3, as specified by the RM rules, but objects of this type will have a size of 8 (Smallint'Object_Size = 8), since objects by default occupy an integral number of storage units. On some targets, notably older versions of the Digital Alpha, the size of stand alone objects of this type may be 32, reflecting the inability of the hardware to do byte load/stores.

Similarly, the size of type Rec is 40 bits (Rec'Size = Rec'Value_Size = 40), but the alignment is 4, so objects of this type will have their size increased to 64 bits so that it is a multiple of the alignment (in bits). This decision is in accordance with the specific Implementation Advice in RM 13.3(43):

“A Size clause should be supported for an object if the specified Size is at least as large as its subtype’s Size, and corresponds to a size in storage elements that is a multiple of the object’s Alignment (if the Alignment is nonzero).”

An explicit size clause may be used to override the default size by increasing it. For example, if we have:

type My_Boolean is new Boolean;
for My_Boolean'Size use 32;

then values of this type will always be 32-bit long. In the case of discrete types, the size can be increased up to 64 bits on 32-bit targets and 128 bits on 64-bit targets, with the effect that the entire specified field is used to hold the value, sign- or zero-extended as appropriate. If more than 64 bits or 128 bits resp. is specified, then padding space is allocated after the value, and a warning is issued that there are unused bits.

Similarly the size of records and arrays may be increased, and the effect is to add padding bits after the value. This also causes a warning message to be generated.

The largest Size value permitted in GNAT is 2**31-1. Since this is a Size in bits, this corresponds to an object of size 256 megabytes (minus one). This limitation is true on all targets. The reason for this limitation is that it improves the quality of the code in many cases if it is known that a Size value can be accommodated in an object of type Integer.

9.3. Storage_Size Clauses

For tasks, the Storage_Size clause specifies the amount of space to be allocated for the task stack. This cannot be extended, and if the stack is exhausted, then Storage_Error will be raised (if stack checking is enabled). Use a Storage_Size attribute definition clause, or a Storage_Size pragma in the task definition to set the appropriate required size. A useful technique is to include in every task definition a pragma of the form:

pragma Storage_Size (Default_Stack_Size);

Then Default_Stack_Size can be defined in a global package, and modified as required. Any tasks requiring stack sizes different from the default can have an appropriate alternative reference in the pragma.

You can also use the -d binder switch to modify the default stack size.

For access types, the Storage_Size clause specifies the maximum space available for allocation of objects of the type. If this space is exceeded then Storage_Error will be raised by an allocation attempt. In the case where the access type is declared local to a subprogram, the use of a Storage_Size clause triggers automatic use of a special predefined storage pool (System.Pool_Size) that ensures that all space for the pool is automatically reclaimed on exit from the scope in which the type is declared.

A special case recognized by the compiler is the specification of a Storage_Size of zero for an access type. This means that no items can be allocated from the pool, and this is recognized at compile time, and all the overhead normally associated with maintaining a fixed size storage pool is eliminated. Consider the following example:

procedure p is
   type R is array (Natural) of Character;
   type P is access all R;
   for P'Storage_Size use 0;
   --  Above access type intended only for interfacing purposes

   y : P;

   procedure g (m : P);
   pragma Import (C, g);

   --  ...

begin
   --  ...
   y := new R;
end;

As indicated in this example, these dummy storage pools are often useful in connection with interfacing where no object will ever be allocated. If you compile the above example, you get the warning:

p.adb:16:09: warning: allocation from empty storage pool
p.adb:16:09: warning: Storage_Error will be raised at run time

Of course in practice, there will not be any explicit allocators in the case of such an access declaration.

9.4. Size of Variant Record Objects

In the case of variant record objects, there is a question whether Size gives information about a particular variant, or the maximum size required for any variant. Consider the following program

with Text_IO; use Text_IO;
procedure q is
   type R1 (A : Boolean := False) is record
     case A is
       when True  => X : Character;
       when False => null;
     end case;
   end record;

   V1 : R1 (False);
   V2 : R1;

begin
   Put_Line (Integer'Image (V1'Size));
   Put_Line (Integer'Image (V2'Size));
end q;

Here we are dealing with a variant record, where the True variant requires 16 bits, and the False variant requires 8 bits. In the above example, both V1 and V2 contain the False variant, which is only 8 bits long. However, the result of running the program is:

8
16

The reason for the difference here is that the discriminant value of V1 is fixed, and will always be False. It is not possible to assign a True variant value to V1, therefore 8 bits is sufficient. On the other hand, in the case of V2, the initial discriminant value is False (from the default), but it is possible to assign a True variant value to V2, therefore 16 bits must be allocated for V2 in the general case, even fewer bits may be needed at any particular point during the program execution.

As can be seen from the output of this program, the 'Size attribute applied to such an object in GNAT gives the actual allocated size of the variable, which is the largest size of any of the variants. The Ada Reference Manual is not completely clear on what choice should be made here, but the GNAT behavior seems most consistent with the language in the RM.

In some cases, it may be desirable to obtain the size of the current variant, rather than the size of the largest variant. This can be achieved in GNAT by making use of the fact that in the case of a subprogram parameter, GNAT does indeed return the size of the current variant (because a subprogram has no way of knowing how much space is actually allocated for the actual).

Consider the following modified version of the above program:

with Text_IO; use Text_IO;
procedure q is
   type R1 (A : Boolean := False) is record
     case A is
       when True  => X : Character;
       when False => null;
     end case;
   end record;

   V2 : R1;

   function Size (V : R1) return Integer is
   begin
      return V'Size;
   end Size;

begin
   Put_Line (Integer'Image (V2'Size));
   Put_Line (Integer'Image (Size (V2)));
   V2 := (True, 'x');
   Put_Line (Integer'Image (V2'Size));
   Put_Line (Integer'Image (Size (V2)));
end q;

The output from this program is

16
8
16
16

Here we see that while the 'Size attribute always returns the maximum size, regardless of the current variant value, the Size function does indeed return the size of the current variant value.

9.5. Biased Representation

In the case of scalars with a range starting at other than zero, it is possible in some cases to specify a size smaller than the default minimum value, and in such cases, GNAT uses an unsigned biased representation, in which zero is used to represent the lower bound, and successive values represent successive values of the type.

For example, suppose we have the declaration:

type Small is range -7 .. -4;
for Small'Size use 2;

Although the default size of type Small is 4, the Size clause is accepted by GNAT and results in the following representation scheme:

-7 is represented as 2#00#
-6 is represented as 2#01#
-5 is represented as 2#10#
-4 is represented as 2#11#

Biased representation is only used if the specified Size clause cannot be accepted in any other manner. These reduced sizes that force biased representation can be used for all discrete types except for enumeration types for which a representation clause is given.

9.6. Value_Size and Object_Size Clauses

In Ada 95 and Ada 2005, T'Size for a type T is the minimum number of bits required to hold values of type T. Although this interpretation was allowed in Ada 83, it was not required, and this requirement in practice can cause some significant difficulties. For example, in most Ada 83 compilers, Natural'Size was 32. However, in Ada 95 and Ada 2005, Natural'Size is typically 31. This means that code may change in behavior when moving from Ada 83 to Ada 95 or Ada 2005. For example, consider:

type Rec is record
   A : Natural;
   B : Natural;
end record;

for Rec use record
   A at 0 range 0 .. Natural'Size - 1;
   B at 0 range Natural'Size .. 2 * Natural'Size - 1;
end record;

In the above code, since the typical size of Natural objects is 32 bits and Natural'Size is 31, the above code can cause unexpected inefficient packing in Ada 95 and Ada 2005, and in general there are cases where the fact that the object size can exceed the size of the type causes surprises.

To help get around this problem GNAT provides two implementation defined attributes, Value_Size and Object_Size. When applied to a type, these attributes yield the size of the type (corresponding to the RM defined size attribute), and the size of objects of the type respectively.

The Object_Size is used for determining the default size of objects and components. This size value can be referred to using the Object_Size attribute. The phrase ‘is used’ here means that it is the basis of the determination of the size. The backend is free to pad this up if necessary for efficiency, e.g., an 8-bit stand-alone character might be stored in 32 bits on a machine with no efficient byte access instructions such as the Alpha.

The default rules for the value of Object_Size for discrete types are as follows:

  • The Object_Size for base subtypes reflect the natural hardware size in bits (run the compiler with -gnatS to find those values for numeric types). Enumeration types and fixed-point base subtypes have 8, 16, 32, or 64 bits for this size, depending on the range of values to be stored.

  • The Object_Size of a subtype is the same as the Object_Size of the type from which it is obtained.

  • The Object_Size of a derived base type is copied from the parent base type, and the Object_Size of a derived first subtype is copied from the parent first subtype.

The Value_Size attribute is the (minimum) number of bits required to store a value of the type. This value is used to determine how tightly to pack records or arrays with components of this type, and also affects the semantics of unchecked conversion (unchecked conversions where the Value_Size values differ generate a warning, and are potentially target dependent).

The default rules for the value of Value_Size are as follows:

  • The Value_Size for a base subtype is the minimum number of bits required to store all values of the type (including the sign bit only if negative values are possible).

  • If a subtype statically matches the first subtype of a given type, then it has by default the same Value_Size as the first subtype. (This is a consequence of RM 13.1(14): “if two subtypes statically match, then their subtype-specific aspects are the same”.)

  • All other subtypes have a Value_Size corresponding to the minimum number of bits required to store all values of the subtype. For dynamic bounds, it is assumed that the value can range down or up to the corresponding bound of the ancestor

The RM defined attribute Size corresponds to the Value_Size attribute.

The Size attribute may be defined for a first-named subtype. This sets the Value_Size of the first-named subtype to the given value, and the Object_Size of this first-named subtype to the given value padded up to an appropriate boundary. It is a consequence of the default rules above that this Object_Size will apply to all further subtypes. On the other hand, Value_Size is affected only for the first subtype, any dynamic subtypes obtained from it directly, and any statically matching subtypes. The Value_Size of any other static subtypes is not affected.

Value_Size and Object_Size may be explicitly set for any subtype using an attribute definition clause. Note that the use of these attributes can cause the RM 13.1(14) rule to be violated. If two access types reference aliased objects whose subtypes have differing Object_Size values as a result of explicit attribute definition clauses, then it is illegal to convert from one access subtype to the other. For a more complete description of this additional legality rule, see the description of the Object_Size attribute.

To get a feel for the difference, consider the following examples (note that in each case the base is Short_Short_Integer with a size of 8):

Type or subtype declaration

Object_Size

Value_Size

type x1 is range 0 .. 5;

8

3

type x2 is range 0 .. 5; for x2'size use 12;

16

12

subtype x3 is x2 range 0 .. 3;

16

2

subtype x4 is x2'base range 0 .. 10;

8

4

dynamic : x2'Base range -64 .. +63;

subtype x5 is x2 range 0 .. dynamic;

16

3*

subtype x6 is x2'base range 0 .. dynamic;

8

7*

Note: the entries marked ‘*’ are not actually specified by the Ada Reference Manual, which has nothing to say about size in the dynamic case. What GNAT does is to allocate sufficient bits to accommodate any possible dynamic values for the bounds at run-time.

So far, so good, but GNAT has to obey the RM rules, so the question is under what conditions must the RM Size be used. The following is a list of the occasions on which the RM Size must be used:

  • Component size for packed arrays or records

  • Value of the attribute Size for a type

  • Warning about sizes not matching for unchecked conversion

For record types, the Object_Size is always a multiple of the alignment of the type (this is true for all types). In some cases the Value_Size can be smaller. Consider:

type R is record
  X : Integer;
  Y : Character;
end record;

On a typical 32-bit architecture, the X component will occupy four bytes and the Y component will occupy one byte, for a total of 5 bytes. As a result R'Value_Size will be 40 (bits) since this is the minimum size required to store a value of this type. For example, it is permissible to have a component of type R in an array whose component size is specified to be 40 bits.

However, R'Object_Size will be 64 (bits). The difference is due to the alignment requirement for objects of the record type. The X component will require four-byte alignment because that is what type Integer requires, whereas the Y component, a Character, will only require 1-byte alignment. Since the alignment required for X is the greatest of all the components’ alignments, that is the alignment required for the enclosing record type, i.e., 4 bytes or 32 bits. As indicated above, the actual object size must be rounded up so that it is a multiple of the alignment value. Therefore, 40 bits rounded up to the next multiple of 32 yields 64 bits.

For all other types, the Object_Size and Value_Size are the same (and equivalent to the RM attribute Size). Only Size may be specified for such types.

Note that Value_Size can be used to force biased representation for a particular subtype. Consider this example:

type R is (A, B, C, D, E, F);
subtype RAB is R range A .. B;
subtype REF is R range E .. F;

By default, RAB has a size of 1 (sufficient to accommodate the representation of A and B, 0 and 1), and REF has a size of 3 (sufficient to accommodate the representation of E and F, 4 and 5). But if we add the following Value_Size attribute definition clause:

for REF'Value_Size use 1;

then biased representation is forced for REF, and 0 will represent E and 1 will represent F. A warning is issued when a Value_Size attribute definition clause forces biased representation. This warning can be turned off using -gnatw.B.

9.7. Component_Size Clauses

Normally, the value specified in a component size clause must be consistent with the subtype of the array component with regard to size and alignment. In other words, the value specified must be at least equal to the size of this subtype, and must be a multiple of the alignment value.

In addition, component size clauses are allowed which cause the array to be packed, by specifying a smaller value. A first case is for component size values in the range 1 through 63 on 32-bit targets, and 1 through 127 on 64-bit targets. The value specified may not be smaller than the Size of the subtype. GNAT will accurately honor all packing requests in this range. For example, if we have:

type r is array (1 .. 8) of Natural;
for r'Component_Size use 31;

then the resulting array has a length of 31 bytes (248 bits = 8 * 31). Of course access to the components of such an array is considerably less efficient than if the natural component size of 32 is used. A second case is when the subtype of the component is a record type padded because of its default alignment. For example, if we have:

type r is record
  i : Integer;
  j : Integer;
  b : Boolean;
end record;

type a is array (1 .. 8) of r;
for a'Component_Size use 72;

then the resulting array has a length of 72 bytes, instead of 96 bytes if the alignment of the record (4) was obeyed.

Note that there is no point in giving both a component size clause and a pragma Pack for the same array type. if such duplicate clauses are given, the pragma Pack will be ignored.

9.8. Bit_Order Clauses

For record subtypes, GNAT permits the specification of the Bit_Order attribute. The specification may either correspond to the default bit order for the target, in which case the specification has no effect and places no additional restrictions, or it may be for the non-standard setting (that is the opposite of the default).

In the case where the non-standard value is specified, the effect is to renumber bits within each byte, but the ordering of bytes is not affected. There are certain restrictions placed on component clauses as follows:

  • Components fitting within a single storage unit.

    These are unrestricted, and the effect is merely to renumber bits. For example if we are on a little-endian machine with Low_Order_First being the default, then the following two declarations have exactly the same effect:

    type R1 is record
       A : Boolean;
       B : Integer range 1 .. 120;
    end record;
    
    for R1 use record
       A at 0 range 0 .. 0;
       B at 0 range 1 .. 7;
    end record;
    
    type R2 is record
       A : Boolean;
       B : Integer range 1 .. 120;
    end record;
    
    for R2'Bit_Order use High_Order_First;
    
    for R2 use record
       A at 0 range 7 .. 7;
       B at 0 range 0 .. 6;
    end record;
    

    The useful application here is to write the second declaration with the Bit_Order attribute definition clause, and know that it will be treated the same, regardless of whether the target is little-endian or big-endian.

  • Components occupying an integral number of bytes.

    These are components that exactly fit in two or more bytes. Such component declarations are allowed, but have no effect, since it is important to realize that the Bit_Order specification does not affect the ordering of bytes. In particular, the following attempt at getting an endian-independent integer does not work:

    type R2 is record
       A : Integer;
    end record;
    
    for R2'Bit_Order use High_Order_First;
    
    for R2 use record
       A at 0 range 0 .. 31;
    end record;
    

    This declaration will result in a little-endian integer on a little-endian machine, and a big-endian integer on a big-endian machine. If byte flipping is required for interoperability between big- and little-endian machines, this must be explicitly programmed. This capability is not provided by Bit_Order.

  • Components that are positioned across byte boundaries.

    but do not occupy an integral number of bytes. Given that bytes are not reordered, such fields would occupy a non-contiguous sequence of bits in memory, requiring non-trivial code to reassemble. They are for this reason not permitted, and any component clause specifying such a layout will be flagged as illegal by GNAT.

Since the misconception that Bit_Order automatically deals with all endian-related incompatibilities is a common one, the specification of a component field that is an integral number of bytes will always generate a warning. This warning may be suppressed using pragma Warnings (Off) if desired. The following section contains additional details regarding the issue of byte ordering.

9.9. Effect of Bit_Order on Byte Ordering

In this section we will review the effect of the Bit_Order attribute definition clause on byte ordering. Briefly, it has no effect at all, but a detailed example will be helpful. Before giving this example, let us review the precise definition of the effect of defining Bit_Order. The effect of a non-standard bit order is described in section 13.5.3 of the Ada Reference Manual:

“2 A bit ordering is a method of interpreting the meaning of the storage place attributes.”

To understand the precise definition of storage place attributes in this context, we visit section 13.5.1 of the manual:

“13 A record_representation_clause (without the mod_clause) specifies the layout. The storage place attributes (see 13.5.2) are taken from the values of the position, first_bit, and last_bit expressions after normalizing those values so that first_bit is less than Storage_Unit.”

The critical point here is that storage places are taken from the values after normalization, not before. So the Bit_Order interpretation applies to normalized values. The interpretation is described in the later part of the 13.5.3 paragraph:

“2 A bit ordering is a method of interpreting the meaning of the storage place attributes. High_Order_First (known in the vernacular as ‘big endian’) means that the first bit of a storage element (bit 0) is the most significant bit (interpreting the sequence of bits that represent a component as an unsigned integer value). Low_Order_First (known in the vernacular as ‘little endian’) means the opposite: the first bit is the least significant.”

Note that the numbering is with respect to the bits of a storage unit. In other words, the specification affects only the numbering of bits within a single storage unit.

We can make the effect clearer by giving an example.

Suppose that we have an external device which presents two bytes, the first byte presented, which is the first (low addressed byte) of the two byte record is called Master, and the second byte is called Slave.

The left most (most significant) bit is called Control for each byte, and the remaining 7 bits are called V1, V2, … V7, where V7 is the rightmost (least significant) bit.

On a big-endian machine, we can write the following representation clause

type Data is record
   Master_Control : Bit;
   Master_V1      : Bit;
   Master_V2      : Bit;
   Master_V3      : Bit;
   Master_V4      : Bit;
   Master_V5      : Bit;
   Master_V6      : Bit;
   Master_V7      : Bit;
   Slave_Control  : Bit;
   Slave_V1       : Bit;
   Slave_V2       : Bit;
   Slave_V3       : Bit;
   Slave_V4       : Bit;
   Slave_V5       : Bit;
   Slave_V6       : Bit;
   Slave_V7       : Bit;
end record;

for Data use record
   Master_Control at 0 range 0 .. 0;
   Master_V1      at 0 range 1 .. 1;
   Master_V2      at 0 range 2 .. 2;
   Master_V3      at 0 range 3 .. 3;
   Master_V4      at 0 range 4 .. 4;
   Master_V5      at 0 range 5 .. 5;
   Master_V6      at 0 range 6 .. 6;
   Master_V7      at 0 range 7 .. 7;
   Slave_Control  at 1 range 0 .. 0;
   Slave_V1       at 1 range 1 .. 1;
   Slave_V2       at 1 range 2 .. 2;
   Slave_V3       at 1 range 3 .. 3;
   Slave_V4       at 1 range 4 .. 4;
   Slave_V5       at 1 range 5 .. 5;
   Slave_V6       at 1 range 6 .. 6;
   Slave_V7       at 1 range 7 .. 7;
end record;

Now if we move this to a little endian machine, then the bit ordering within the byte is backwards, so we have to rewrite the record rep clause as:

for Data use record
   Master_Control at 0 range 7 .. 7;
   Master_V1      at 0 range 6 .. 6;
   Master_V2      at 0 range 5 .. 5;
   Master_V3      at 0 range 4 .. 4;
   Master_V4      at 0 range 3 .. 3;
   Master_V5      at 0 range 2 .. 2;
   Master_V6      at 0 range 1 .. 1;
   Master_V7      at 0 range 0 .. 0;
   Slave_Control  at 1 range 7 .. 7;
   Slave_V1       at 1 range 6 .. 6;
   Slave_V2       at 1 range 5 .. 5;
   Slave_V3       at 1 range 4 .. 4;
   Slave_V4       at 1 range 3 .. 3;
   Slave_V5       at 1 range 2 .. 2;
   Slave_V6       at 1 range 1 .. 1;
   Slave_V7       at 1 range 0 .. 0;
end record;

It is a nuisance to have to rewrite the clause, especially if the code has to be maintained on both machines. However, this is a case that we can handle with the Bit_Order attribute if it is implemented. Note that the implementation is not required on byte addressed machines, but it is indeed implemented in GNAT. This means that we can simply use the first record clause, together with the declaration

for Data'Bit_Order use High_Order_First;

and the effect is what is desired, namely the layout is exactly the same, independent of whether the code is compiled on a big-endian or little-endian machine.

The important point to understand is that byte ordering is not affected. A Bit_Order attribute definition never affects which byte a field ends up in, only where it ends up in that byte. To make this clear, let us rewrite the record rep clause of the previous example as:

for Data'Bit_Order use High_Order_First;
for Data use record
   Master_Control at 0 range  0 .. 0;
   Master_V1      at 0 range  1 .. 1;
   Master_V2      at 0 range  2 .. 2;
   Master_V3      at 0 range  3 .. 3;
   Master_V4      at 0 range  4 .. 4;
   Master_V5      at 0 range  5 .. 5;
   Master_V6      at 0 range  6 .. 6;
   Master_V7      at 0 range  7 .. 7;
   Slave_Control  at 0 range  8 .. 8;
   Slave_V1       at 0 range  9 .. 9;
   Slave_V2       at 0 range 10 .. 10;
   Slave_V3       at 0 range 11 .. 11;
   Slave_V4       at 0 range 12 .. 12;
   Slave_V5       at 0 range 13 .. 13;
   Slave_V6       at 0 range 14 .. 14;
   Slave_V7       at 0 range 15 .. 15;
end record;

This is exactly equivalent to saying (a repeat of the first example):

for Data'Bit_Order use High_Order_First;
for Data use record
   Master_Control at 0 range 0 .. 0;
   Master_V1      at 0 range 1 .. 1;
   Master_V2      at 0 range 2 .. 2;
   Master_V3      at 0 range 3 .. 3;
   Master_V4      at 0 range 4 .. 4;
   Master_V5      at 0 range 5 .. 5;
   Master_V6      at 0 range 6 .. 6;
   Master_V7      at 0 range 7 .. 7;
   Slave_Control  at 1 range 0 .. 0;
   Slave_V1       at 1 range 1 .. 1;
   Slave_V2       at 1 range 2 .. 2;
   Slave_V3       at 1 range 3 .. 3;
   Slave_V4       at 1 range 4 .. 4;
   Slave_V5       at 1 range 5 .. 5;
   Slave_V6       at 1 range 6 .. 6;
   Slave_V7       at 1 range 7 .. 7;
end record;

Why are they equivalent? Well take a specific field, the Slave_V2 field. The storage place attributes are obtained by normalizing the values given so that the First_Bit value is less than 8. After normalizing the values (0,10,10) we get (1,2,2) which is exactly what we specified in the other case.

Now one might expect that the Bit_Order attribute might affect bit numbering within the entire record component (two bytes in this case, thus affecting which byte fields end up in), but that is not the way this feature is defined, it only affects numbering of bits, not which byte they end up in.

Consequently it never makes sense to specify a starting bit number greater than 7 (for a byte addressable field) if an attribute definition for Bit_Order has been given, and indeed it may be actively confusing to specify such a value, so the compiler generates a warning for such usage.

If you do need to control byte ordering then appropriate conditional values must be used. If in our example, the slave byte came first on some machines we might write:

Master_Byte_First constant Boolean := ...;

Master_Byte : constant Natural :=
                1 - Boolean'Pos (Master_Byte_First);
Slave_Byte  : constant Natural :=
                Boolean'Pos (Master_Byte_First);

for Data'Bit_Order use High_Order_First;
for Data use record
   Master_Control at Master_Byte range 0 .. 0;
   Master_V1      at Master_Byte range 1 .. 1;
   Master_V2      at Master_Byte range 2 .. 2;
   Master_V3      at Master_Byte range 3 .. 3;
   Master_V4      at Master_Byte range 4 .. 4;
   Master_V5      at Master_Byte range 5 .. 5;
   Master_V6      at Master_Byte range 6 .. 6;
   Master_V7      at Master_Byte range 7 .. 7;
   Slave_Control  at Slave_Byte  range 0 .. 0;
   Slave_V1       at Slave_Byte  range 1 .. 1;
   Slave_V2       at Slave_Byte  range 2 .. 2;
   Slave_V3       at Slave_Byte  range 3 .. 3;
   Slave_V4       at Slave_Byte  range 4 .. 4;
   Slave_V5       at Slave_Byte  range 5 .. 5;
   Slave_V6       at Slave_Byte  range 6 .. 6;
   Slave_V7       at Slave_Byte  range 7 .. 7;
end record;

Now to switch between machines, all that is necessary is to set the boolean constant Master_Byte_First in an appropriate manner.

9.10. Pragma Pack for Arrays

Pragma Pack applied to an array has an effect that depends upon whether the component type is packable. For a component type to be packable, it must be one of the following cases:

  • Any elementary type.

  • Any small packed array type with a static size.

  • Any small simple record type with a static size.

For all these cases, if the component subtype size is in the range 1 through 63 on 32-bit targets, and 1 through 127 on 64-bit targets, then the effect of the pragma Pack is exactly as though a component size were specified giving the component subtype size.

All other types are non-packable, they occupy an integral number of storage units and the only effect of pragma Pack is to remove alignment gaps.

For example if we have:

type r is range 0 .. 17;

type ar is array (1 .. 8) of r;
pragma Pack (ar);

Then the component size of ar will be set to 5 (i.e., to r'size, and the size of the array ar will be exactly 40 bits).

Note that in some cases this rather fierce approach to packing can produce unexpected effects. For example, in Ada 95 and Ada 2005, subtype Natural typically has a size of 31, meaning that if you pack an array of Natural, you get 31-bit close packing, which saves a few bits, but results in far less efficient access. Since many other Ada compilers will ignore such a packing request, GNAT will generate a warning on some uses of pragma Pack that it guesses might not be what is intended. You can easily remove this warning by using an explicit Component_Size setting instead, which never generates a warning, since the intention of the programmer is clear in this case.

GNAT treats packed arrays in one of two ways. If the size of the array is known at compile time and is at most 64 bits on 32-bit targets, and at most 128 bits on 64-bit targets, then internally the array is represented as a single modular type, of exactly the appropriate number of bits. If the length is greater than 64 bits on 32-bit targets, and greater than 128 bits on 64-bit targets, or is not known at compile time, then the packed array is represented as an array of bytes, and its length is always a multiple of 8 bits.

Note that to represent a packed array as a modular type, the alignment must be suitable for the modular type involved. For example, on typical machines a 32-bit packed array will be represented by a 32-bit modular integer with an alignment of four bytes. If you explicitly override the default alignment with an alignment clause that is too small, the modular representation cannot be used. For example, consider the following set of declarations:

type R is range 1 .. 3;
type S is array (1 .. 31) of R;
for S'Component_Size use 2;
for S'Size use 62;
for S'Alignment use 1;

If the alignment clause were not present, then a 62-bit modular representation would be chosen (typically with an alignment of 4 or 8 bytes depending on the target). But the default alignment is overridden with the explicit alignment clause. This means that the modular representation cannot be used, and instead the array of bytes representation must be used, meaning that the length must be a multiple of 8. Thus the above set of declarations will result in a diagnostic rejecting the size clause and noting that the minimum size allowed is 64.

One special case that is worth noting occurs when the base type of the component size is 8/16/32 and the subtype is one bit less. Notably this occurs with subtype Natural. Consider:

type Arr is array (1 .. 32) of Natural;
pragma Pack (Arr);

In all commonly used Ada 83 compilers, this pragma Pack would be ignored, since typically Natural'Size is 32 in Ada 83, and in any case most Ada 83 compilers did not attempt 31 bit packing.

In Ada 95 and Ada 2005, Natural'Size is required to be 31. Furthermore, GNAT really does pack 31-bit subtype to 31 bits. This may result in a substantial unintended performance penalty when porting legacy Ada 83 code. To help prevent this, GNAT generates a warning in such cases. If you really want 31 bit packing in a case like this, you can set the component size explicitly:

type Arr is array (1 .. 32) of Natural;
for Arr'Component_Size use 31;

Here 31-bit packing is achieved as required, and no warning is generated, since in this case the programmer intention is clear.

9.11. Pragma Pack for Records

Pragma Pack applied to a record will pack the components to reduce wasted space from alignment gaps and by reducing the amount of space taken by components. We distinguish between packable components and non-packable components. Components of the following types are considered packable:

  • Components of an elementary type are packable unless they are aliased, independent or atomic.

  • Small packed arrays, where the size is statically known, are represented internally as modular integers, and so they are also packable.

  • Small simple records, where the size is statically known, are also packable.

For all these cases, if the 'Size value is in the range 1 through 64 on 32-bit targets, and 1 through 128 on 64-bit targets, the components occupy the exact number of bits corresponding to this value and are packed with no padding bits, i.e. they can start on an arbitrary bit boundary.

All other types are non-packable, they occupy an integral number of storage units and the only effect of pragma Pack is to remove alignment gaps.

For example, consider the record

type Rb1 is array (1 .. 13) of Boolean;
pragma Pack (Rb1);

type Rb2 is array (1 .. 65) of Boolean;
pragma Pack (Rb2);

type AF is new Float with Atomic;

type X2 is record
   L1 : Boolean;
   L2 : Duration;
   L3 : AF;
   L4 : Boolean;
   L5 : Rb1;
   L6 : Rb2;
end record;
pragma Pack (X2);

The representation for the record X2 is as follows on 32-bit targets:

for X2'Size use 224;
for X2 use record
   L1 at  0 range  0 .. 0;
   L2 at  0 range  1 .. 64;
   L3 at 12 range  0 .. 31;
   L4 at 16 range  0 .. 0;
   L5 at 16 range  1 .. 13;
   L6 at 18 range  0 .. 71;
end record;

Studying this example, we see that the packable fields L1 and L2 are of length equal to their sizes, and placed at specific bit boundaries (and not byte boundaries) to eliminate padding. But L3 is of a non-packable float type (because it is aliased), so it is on the next appropriate alignment boundary.

The next two fields are fully packable, so L4 and L5 are minimally packed with no gaps. However, type Rb2 is a packed array that is longer than 64 bits, so it is itself non-packable on 32-bit targets. Thus the L6 field is aligned to the next byte boundary, and takes an integral number of bytes, i.e., 72 bits.

9.12. Record Representation Clauses

Record representation clauses may be given for all record types, including types obtained by record extension. Component clauses are allowed for any static component. The restrictions on component clauses depend on the type of the component.

For all components of an elementary type, the only restriction on component clauses is that the size must be at least the 'Size value of the type (actually the Value_Size). There are no restrictions due to alignment, and such components may freely cross storage boundaries.

Packed arrays with a size up to and including 64 bits on 32-bit targets, and up to and including 128 bits on 64-bit targets, are represented internally using a modular type with the appropriate number of bits, and thus the same lack of restriction applies. For example, if you declare:

type R is array (1 .. 49) of Boolean;
pragma Pack (R);
for R'Size use 49;

then a component clause for a component of type R may start on any specified bit boundary, and may specify a value of 49 bits or greater.

For packed bit arrays that are longer than 64 bits on 32-bit targets, and longer than 128 bits on 64-bit targets, there are two cases. If the component size is a power of 2 (1,2,4,8,16,32,64 bits), including the important case of single bits or boolean values, then there are no limitations on placement of such components, and they may start and end at arbitrary bit boundaries.

If the component size is not a power of 2 (e.g., 3 or 5), then an array of this type must always be placed on on a storage unit (byte) boundary and occupy an integral number of storage units (bytes). Any component clause that does not meet this requirement will be rejected.

Any aliased component, or component of an aliased type, must have its normal alignment and size. A component clause that does not meet this requirement will be rejected.

The tag field of a tagged type always occupies an address sized field at the start of the record. No component clause may attempt to overlay this tag. When a tagged type appears as a component, the tag field must have proper alignment

In the case of a record extension T1, of a type T, no component clause applied to the type T1 can specify a storage location that would overlap the first T'Object_Size bits of the record.

For all other component types, including non-bit-packed arrays, the component can be placed at an arbitrary bit boundary, so for example, the following is permitted:

type R is array (1 .. 10) of Boolean;
for R'Size use 80;

type Q is record
   G, H : Boolean;
   L, M : R;
end record;

for Q use record
   G at 0 range  0 ..   0;
   H at 0 range  1 ..   1;
   L at 0 range  2 ..  81;
   R at 0 range 82 .. 161;
end record;

9.13. Handling of Records with Holes

As a result of alignment considerations, records may contain “holes” or gaps which do not correspond to the data bits of any of the components. Record representation clauses can also result in holes in records.

GNAT does not attempt to clear these holes, so in record objects, they should be considered to hold undefined rubbish. The generated equality routine just tests components so does not access these undefined bits, and assignment and copy operations may or may not preserve the contents of these holes (for assignments, the holes in the target will in practice contain either the bits that are present in the holes in the source, or the bits that were present in the target before the assignment).

If it is necessary to ensure that holes in records have all zero bits, then record objects for which this initialization is desired should be explicitly set to all zero values using Unchecked_Conversion or address overlays. For example

type HRec is record
   C : Character;
   I : Integer;
end record;

On typical machines, integers need to be aligned on a four-byte boundary, resulting in three bytes of undefined rubbish following the 8-bit field for C. To ensure that the hole in a variable of type HRec is set to all zero bits, you could for example do:

type Base is record
   Dummy1, Dummy2 : Integer := 0;
end record;

BaseVar : Base;
RealVar : Hrec;
for RealVar'Address use BaseVar'Address;

Now the 8-bytes of the value of RealVar start out containing all zero bits. A safer approach is to just define dummy fields, avoiding the holes, as in:

type HRec is record
   C      : Character;
   Dummy1 : Short_Short_Integer := 0;
   Dummy2 : Short_Short_Integer := 0;
   Dummy3 : Short_Short_Integer := 0;
   I      : Integer;
end record;

And to make absolutely sure that the intent of this is followed, you can use representation clauses:

for Hrec use record
   C      at 0 range 0 .. 7;
   Dummy1 at 1 range 0 .. 7;
   Dummy2 at 2 range 0 .. 7;
   Dummy3 at 3 range 0 .. 7;
   I      at 4 range 0 .. 31;
end record;
for Hrec'Size use 64;

9.14. Enumeration Clauses

The only restriction on enumeration clauses is that the range of values must be representable. For the signed case, if one or more of the representation values are negative, all values must be in the range:

System.Min_Int .. System.Max_Int

For the unsigned case, where all values are nonnegative, the values must be in the range:

0 .. System.Max_Binary_Modulus;

A confirming representation clause is one in which the values range from 0 in sequence, i.e., a clause that confirms the default representation for an enumeration type. Such a confirming representation is permitted by these rules, and is specially recognized by the compiler so that no extra overhead results from the use of such a clause.

If an array has an index type which is an enumeration type to which an enumeration clause has been applied, then the array is stored in a compact manner. Consider the declarations:

type r is (A, B, C);
for r use (A => 1, B => 5, C => 10);
type t is array (r) of Character;

The array type t corresponds to a vector with exactly three elements and has a default size equal to 3*Character'Size. This ensures efficient use of space, but means that accesses to elements of the array will incur the overhead of converting representation values to the corresponding positional values, (i.e., the value delivered by the Pos attribute).

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:

  • The type of the item is non-elementary (e.g., a record or array).

  • There is explicit or implicit initialization required for the object. Note that access values are always implicitly initialized.

  • The address value is nonstatic. Here GNAT is more permissive than the RM, and allows the address value to be the address of a previously declared stand-alone variable, as long as it does not itself have an address clause.

    Anchor  : Some_Initialized_Type;
    Overlay : Some_Initialized_Type;
    for Overlay'Address use Anchor'Address;
    

    However, the prefix of the address clause cannot be an array component, or a component of a discriminated record.

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.

9.16. Use of Address Clauses for Memory-Mapped I/O

A common pattern is to use an address clause to map an atomic variable to a location in memory that corresponds to a memory-mapped I/O operation or operations, for example:

type Mem_Word is record
   A,B,C,D : Byte;
end record;
pragma Atomic (Mem_Word);
for Mem_Word_Size use 32;

Mem : Mem_Word;
for Mem'Address use some-address;
...
Temp := Mem;
Temp.A := 32;
Mem := Temp;

For a full access (reference or modification) of the variable (Mem) in this case, as in the above examples, GNAT guarantees that the entire atomic word will be accessed, in accordance with the RM C.6(15) clause.

A problem arises with a component access such as:

Mem.A := 32;

Note that the component A is not declared as atomic. This means that it is not clear what this assignment means. It could correspond to full word read and write as given in the first example, or on architectures that supported such an operation it might be a single byte store instruction. The RM does not have anything to say in this situation, and GNAT does not make any guarantee. The code generated may vary from target to target. GNAT will issue a warning in such a case:

Mem.A := 32;
|
>>> warning: access to non-atomic component of atomic array,
    may cause unexpected accesses to atomic object

It is best to be explicit in this situation, by either declaring the components to be atomic if you want the byte store, or explicitly writing the full word access sequence if that is what the hardware requires. Alternatively, if the full word access sequence is required, GNAT also provides the pragma Volatile_Full_Access which can be used in lieu of pragma Atomic and will give the additional guarantee.

9.17. Effect of Convention on Representation

Normally the specification of a foreign language convention for a type or an object has no effect on the chosen representation. In particular, the representation chosen for data in GNAT generally meets the standard system conventions, and for example records are laid out in a manner that is consistent with C. This means that specifying convention C (for example) has no effect.

There are four exceptions to this general rule:

  • Convention Fortran and array subtypes.

    If pragma Convention Fortran is specified for an array subtype, then in accordance with the implementation advice in section 3.6.2(11) of the Ada Reference Manual, the array will be stored in a Fortran-compatible column-major manner, instead of the normal default row-major order.

  • Convention C and enumeration types

    GNAT normally stores enumeration types in 8, 16, or 32 bits as required to accommodate all values of the type. For example, for the enumeration type declared by:

    type Color is (Red, Green, Blue);
    

    8 bits is sufficient to store all values of the type, so by default, objects of type Color will be represented using 8 bits. However, normal C convention is to use 32 bits for all enum values in C, since enum values are essentially of type int. If pragma Convention C is specified for an Ada enumeration type, then the size is modified as necessary (usually to 32 bits) to be consistent with the C convention for enum values.

    Note that this treatment applies only to types. If Convention C is given for an enumeration object, where the enumeration type is not Convention C, then Object_Size bits are allocated. For example, for a normal enumeration type, with less than 256 elements, only 8 bits will be allocated for the object. Since this may be a surprise in terms of what C expects, GNAT will issue a warning in this situation. The warning can be suppressed by giving an explicit size clause specifying the desired size.

  • Convention C/Fortran and Boolean types

    In C, the usual convention for boolean values, that is values used for conditions, is that zero represents false, and nonzero values represent true. In Ada, the normal convention is that two specific values, typically 0/1, are used to represent false/true respectively.

    Fortran has a similar convention for LOGICAL values (any nonzero value represents true).

    To accommodate the Fortran and C conventions, if a pragma Convention specifies C or Fortran convention for a derived Boolean, as in the following example:

    type C_Switch is new Boolean;
    pragma Convention (C, C_Switch);
    

    then the GNAT generated code will treat any nonzero value as true. For truth values generated by GNAT, the conventional value 1 will be used for True, but when one of these values is read, any nonzero value is treated as True.

9.18. Conventions and Anonymous Access Types

The RM is not entirely clear on convention handling in a number of cases, and in particular, it is not clear on the convention to be given to anonymous access types in general, and in particular what is to be done for the case of anonymous access-to-subprogram.

In GNAT, we decide that if an explicit Convention is applied to an object or component, and its type is such an anonymous type, then the convention will apply to this anonymous type as well. This seems to make sense since it is anomolous in any case to have a different convention for an object and its type, and there is clearly no way to explicitly specify a convention for an anonymous type, since it doesn’t have a name to specify!

Furthermore, we decide that if a convention is applied to a record type, then this convention is inherited by any of its components that are of an anonymous access type which do not have an explicitly specified convention.

The following program shows these conventions in action:

package ConvComp is
   type Foo is range 1 .. 10;
   type T1 is record
      A : access function (X : Foo) return Integer;
      B : Integer;
   end record;
   pragma Convention (C, T1);

   type T2 is record
      A : access function (X : Foo) return Integer;
      pragma Convention  (C, A);
      B : Integer;
   end record;
   pragma Convention (COBOL, T2);

   type T3 is record
      A : access function (X : Foo) return Integer;
      pragma Convention  (COBOL, A);
      B : Integer;
   end record;
   pragma Convention (C, T3);

   type T4 is record
      A : access function (X : Foo) return Integer;
      B : Integer;
   end record;
   pragma Convention (COBOL, T4);

   function F (X : Foo) return Integer;
   pragma Convention (C, F);

   function F (X : Foo) return Integer is (13);

   TV1 : T1 := (F'Access, 12);  -- OK
   TV2 : T2 := (F'Access, 13);  -- OK

   TV3 : T3 := (F'Access, 13);  -- ERROR
                |
>>> subprogram "F" has wrong convention
>>> does not match access to subprogram declared at line 17
     38.    TV4 : T4 := (F'Access, 13);  -- ERROR
                |
>>> subprogram "F" has wrong convention
>>> does not match access to subprogram declared at line 24
     39. end ConvComp;

9.19. Determining the Representations chosen by GNAT

Although the descriptions in this section are intended to be complete, it is often easier to simply experiment to see what GNAT accepts and what the effect is on the layout of types and objects.

As required by the Ada RM, if a representation clause is not accepted, then it must be rejected as illegal by the compiler. However, when a representation clause or pragma is accepted, there can still be questions of what the compiler actually does. For example, if a partial record representation clause specifies the location of some components and not others, then where are the non-specified components placed? Or if pragma Pack is used on a record, then exactly where are the resulting fields placed? The section on pragma Pack in this chapter can be used to answer the second question, but it is often easier to just see what the compiler does.

For this purpose, GNAT provides the option -gnatR. If you compile with this option, then the compiler will output information on the actual representations chosen, in a format similar to source representation clauses. For example, if we compile the package:

package q is
   type r (x : boolean) is tagged record
      case x is
         when True => S : String (1 .. 100);
         when False => null;
      end case;
   end record;

   type r2 is new r (false) with record
      y2 : integer;
   end record;

   for r2 use record
      y2 at 16 range 0 .. 31;
   end record;

   type x is record
      y : character;
   end record;

   type x1 is array (1 .. 10) of x;
   for x1'component_size use 11;

   type ia is access integer;

   type Rb1 is array (1 .. 13) of Boolean;
   pragma Pack (rb1);

   type Rb2 is array (1 .. 65) of Boolean;
   pragma Pack (rb2);

   type x2 is record
      l1 : Boolean;
      l2 : Duration;
      l3 : Float;
      l4 : Boolean;
      l5 : Rb1;
      l6 : Rb2;
   end record;
   pragma Pack (x2);
end q;

using the switch -gnatR we obtain the following output:

Representation information for unit q
-------------------------------------

for r'Size use ??;
for r'Alignment use 4;
for r use record
   x    at 4 range  0 .. 7;
   _tag at 0 range  0 .. 31;
   s    at 5 range  0 .. 799;
end record;

for r2'Size use 160;
for r2'Alignment use 4;
for r2 use record
   x       at  4 range  0 .. 7;
   _tag    at  0 range  0 .. 31;
   _parent at  0 range  0 .. 63;
   y2      at 16 range  0 .. 31;
end record;

for x'Size use 8;
for x'Alignment use 1;
for x use record
   y at 0 range  0 .. 7;
end record;

for x1'Size use 112;
for x1'Alignment use 1;
for x1'Component_Size use 11;

for rb1'Size use 13;
for rb1'Alignment use 2;
for rb1'Component_Size use 1;

for rb2'Size use 72;
for rb2'Alignment use 1;
for rb2'Component_Size use 1;

for x2'Size use 224;
for x2'Alignment use 4;
for x2 use record
   l1 at  0 range  0 .. 0;
   l2 at  0 range  1 .. 64;
   l3 at 12 range  0 .. 31;
   l4 at 16 range  0 .. 0;
   l5 at 16 range  1 .. 13;
   l6 at 18 range  0 .. 71;
end record;

The Size values are actually the Object_Size, i.e., the default size that will be allocated for objects of the type. The ?? size for type r indicates that we have a variant record, and the actual size of objects will depend on the discriminant value.

The Alignment values show the actual alignment chosen by the compiler for each record or array type.

The record representation clause for type r shows where all fields are placed, including the compiler generated tag field (whose location cannot be controlled by the programmer).

The record representation clause for the type extension r2 shows all the fields present, including the parent field, which is a copy of the fields of the parent type of r2, i.e., r1.

The component size and size clauses for types rb1 and rb2 show the exact effect of pragma Pack on these arrays, and the record representation clause for type x2 shows how pragma Pack affects this record type.

In some cases, it may be useful to cut and paste the representation clauses generated by the compiler into the original source to fix and guarantee the actual representation to be used.