PowerAda Data Representation and Layout

From OC Systems Wiki!
Jump to: navigation, search


Unless you use representation clauses, do not rely on the order, size, representation or alignment of objects. Objects within records may not be mapped in the order in which they are declared. The compiler reorganizes data to optimize for space. Note that the order of declarations of items can cause a change in the amount of memory required.

Integer Types

Integers are represented as 8-, 16-, or 32-bit twos-complement signed numbers or modular (unsigned) numbers. They are aligned on 8-,16-, or 32-bit boundaries as appropriate. For each object, the compiler chooses a representation that satisfies any range or length clause you specify.

Floating-Point Types

Type FLOAT uses the 32-bit IEEE format, and is aligned on a 32-bit boundary. Type LONG_FLOAT uses the 64-bit IEEE format, and is aligned on a 64-bit boundary. See IBM POWERstation and POWERserver Hardware Technical Reference - General Information (SA23-2643) for details of the IEEE floating-point format.

Fixed-Point Types

Fixed-point objects occupy 8, 16, or 32 bits. If the range and delta of a type is specified such that an object of that type can be represented in 16 bits, the compiler uses 16 bits; otherwise, it uses 32 bits. Fixed-point values are represented by a signed mantissa stored in twos-complement format.

Enumeration Types

Objects of enumeration types are represented as integers. For a given enumeration type, the range of possible values is determined by the number of elements declared in the type unless a representation clause is used. Normally, the first element is represented by a 0, the second by a 1, and so on. = See Chapter 9, "Pragma IMAGES" and "Enumeration Representation Clauses" for more information on the implementation of enumeration types.

Boolean Types

Objects of boolean types are treated as enumeration objects and occupy 8 bits. Boolean values are stored as 0 for FALSE and 1 for TRUE. The least significant bit in the 8 bits stores the boolean value.

Character Types

Objects of character types are treated as enumeration objects and occupy 8 bits. They are stored as unsigned numbers in the range 0 through 255. Wide characters occupy 16 bits.

Access Types

Objects whose type is an access type occupy 32 bits. The value of such an object is the virtual memory address of the allocated object to which it points. The null access value is stored as a 32-bit integer value of numeric zero.

Arrays

Array objects are stored as contiguous data in row-major order. That is, the collections of objects corresponding to the rows of the innermost (or right-most) dimension follow each other sequentially in memory. The amount of storage occupied by an array element is also dependent on the base type of the array; the array is padded to ensure that all elements have the proper alignment for their types.

Records

Before you read this section, familiarize yourself with section 3.8 of the RM95. It introduces the concept of records in Ada and describes both constrained and unconstrained discriminated records. Record objects are mapped as a sequence of components, not necessarily in the order in which they are declared in the source program.

How to Specify the Order of Record Components

By default, the compiler reorders the components of record types -- even components that have the same size or alignment. The compiler also pads objects when necessary to maintain proper alignment of their components. This behavior is fully compliant with the language standard; programs that are erroneous as a result of this reordering are using programming practices that should be avoided, such as assumptions about data representation that may be true for some (but not all) compilers or hardware platforms.

To prevent the compiler from reordering the components of a record type, apply a representation clause, pragma PACK, or the Ada95 pragma CONVENTION. The preferred method is a representation clause, because it is a required part of the language standard. Use this technique for any record types that have to duplicate AIX system data structures.

For cases where you want to preserve the order but do not care about component sizes, you can use pragma CONVENTION, specifying a language convention of C which can be applied to a record type and prevents the compiler from changing the order of components for objects of that type. This pragma will cause the compiler to order the components just as the C compiler would (which is to preserve the order of components)

To avoid migration problems, it is very important that you understand the consequences of component reordering. Any program that makes an assumption about the order of record components without using a record representation clause might produce erroneous results. These assumptions might take one of the following forms:

  • A record such as:
type Memory_Word is record
Byte_1 : Character;
Byte_2 : Character;
end record;
may be represented with Byte_1 first, or with Byte_2 first, and should have a representation clause applied to it to ensure the intended representation is used.
  • Unchecked conversion between records with the same component types. For two records such as:
type First is record
A : Integer;
B : Float;
end record;
type Second is record
C : Integer;
D : Float;
end record;
an UNCHECKED_CONVERSION operation between these types may yield unintended results if the types do not have representation clauses or pragma CONVENTION applied. As a rule, you should avoid UNCHECKED_CONVERSION in such cases because of the likelihood of erroneous execution.
  • Creating a data file using SEQUENTIAL_IO or DIRECT_IO for records without using a representation clause. In this case, a representation clause is preferred because the sizes of data objects may change because of new compiler releases or maintenance, so records you use with the I/O packages should have sizes specified for their components.

Pragma PRESERVE_LAYOUT

Pragma PRESERVE_LAYOUT is a LegacyAda pragma that is now obsolete due to the addition of pragma CONVENTION for types into Ada95. Use pragma CONVENTION for any new code.

The syntax of this pragma is:

pragma PRESERVE_LAYOUT (RecordType);

where RecordType is the simple name of the record type. The pragma must appear before any forcing occurrences of the record type and must be in the same declarative part, package specification, or task specification. You can apply this pragma to a record type that has been packed. If you apply PRESERVE_LAYOUT to a record type that has a record representation clause, the pragma only applies to the components that do not have component clauses. These components appear in the same order as they are declared in the Ada source code, after all the components with component clauses.

For example, if you want to use UNCHECKED_CONVERSION between these two types:

type FLOAT_ARRAY is array (0..9) of FLOAT;
type FLOAT_RECORD IS
  record
    F0, F1, F2, F3, F4, F5, F6, F7, F8, F9 : FLOAT;
  end record;

you must consider component reordering, or you might make the erroneous assumption that element 1 of FLOAT_ARRAY is at the same offset as component F1 of FLOAT_RECORD. As a rule, you should avoid UNCHECKED_CONVERSION in such cases because of the likelihood of erroneous execution. To ensure that the components are not reordered, add the following entry after the record type declaration:

pragma PRESERVE_LAYOUT(FLOAT_RECORD);

Restrictions on Dynamically Constrained Records

When you declare a constrained record object, the compiler determines the object's representation at compile time and allocates the minimum amount of space needed for the object.

However when the compiler does not have enough information to limit the record to a single representation, it infers a maximum size based on the type information available and allocates that much space for the object. Such a situation arises when you declare a record that contains an array whose size depends on a discriminant of the record.

There are two cases to consider:

No default is provided for the discriminant. In this case, the type is truly unconstrained and objects of this type cannot be declared without explicitly specifying the discriminant in the object declaration, or using a constrained subtype.

A default is provided for the discriminant. In this case, an object can be declared without specifying an explicit value for the discriminant.

In the first case above, if the size might be very large--for instance if an array within a record could contain INTEGER'RANGE elements--then each assignment to the discriminant field at run time will allocate a new object from the heap, assign the contents of the record to it, and return the old object to the heap.

However, in the second case, the compiler attempts to perform size computations and object layout using the maximum possible size of the object. This maximum size must be less than SYSTEM.MAX_INT (which is INTEGER'LAST: 2**32 - 1). If the maximum size of a dynamically constrained record object is greater than SYSTEM.MAX_INT, the compiler will reject the object declaration. Furthermore, if such a potentially large record is a component of another record, the compiler will reject the enclosing record type declaration.

There are two ways to work around the problem of having large blocks of storage allocated for records containing unconstrained arrays, corresponding to the cases above:

Avoid specifying default discriminant values in the declarations of such records. If you do not specify a default value for the discriminant in the type declaration, the program will compute the constraint information during run time. After that it treats the type as constrained and uses only the amount of storage dictated by the discriminant each time you declare an object of that type.

This alternative limits you by preventing you from declaring variables to which you can assign arbitrary instances of the type. For instance, once you declare an object that contains an array with 5 elements, you cannot assign a different value where the corresponding array holds 10 elements.

Declare the type with default discriminant values, but constrain the discriminant type to as small a range as possible. If you need to use record objects to which you can assign differently-sized values, you must declare the type with default values for any discriminants. In this case, you must limit the maximum size of the large array components by constraining the range of the discriminant(s) that define the array bounds.

A variable-length string implementation is an example of this second case:

package Strings is
   type Varstring (L : Natural := 0) is 
   record
      S : String(1 .. L);
   end record;
   VS : Varstring;
end Strings;

This will be rejected at compile time:

$ ada strings.ada
   7:    VS : Varstring;
>>> SEMANTIC: Declarative region too large.

Instead, you must do something like the following:

...
subtype Varstring_Range is
Integer range 0 .. 1_000_000;
type Varstring (L : Varstring_Range := 0) is
...