Memory Management

Maximum allocation size and alignment

We've seen details about the Size and Object_Size attributes in the section about data representation. Later on, we also mentioned the Storage_Size attribute.

In this section, we expand our discussion on sizes and talk about the Max_Size_In_Storage_Elements and the Max_Alignment_For_Allocation attributes. These attributes return values that are important in the allocation of memory subpools via the Allocate procedure from the System.Storage_Pools and the System.Storage_Pools.Subpools packages:

procedure Allocate(
  Pool                     : in out Root_Storage_Pool;
  Storage_Address          :    out Address;
  Size_In_Storage_Elements :        Storage_Elements.Storage_Count;
  Alignment                :        Storage_Elements.Storage_Count);

procedure Allocate (
  Pool                     : in out Root_Storage_Pool_With_Subpools;
  Storage_Address          :    out Address;
  Size_In_Storage_Elements :        Storage_Elements.Storage_Count;
  Alignment                :        Storage_Elements.Storage_Count);

In fact, the Max_Size_In_Storage_Elements attribute indicates the maximum value that can be used for the actual Size_In_Storage_Elements parameter of the Allocate procedure . Likewise, the Max_Alignment_For_Allocation attribute indicates the maximum value for the actual Alignment parameter of the Allocate procedure. (We discuss more details about this procedure later on.)

The Allocate procedure is called when we allocate memory for access types. Therefore, the value returned by the Max_Size_In_Storage_Elements attribute for a subtype S indicates the maximum value of storage elements when allocating memory for an access type whose designated subtype is S, while the Max_Alignment_For_Allocation attribute indicates the maximum alignment that we can use when we allocate memory via the new allocator.

Code example with scalar type

Let's see a simple type T and two types based on it — an array and an access type:

    
    
    
        
package Custom_Types is type T is new Integer; type T_Array is array (Positive range <>) of T; type T_Access is access T; end Custom_Types;

The test procedure Show_Sizes shows the values returned by the Size, Max_Size_In_Storage_Elements, and Max_Alignment_For_Allocation attributes for the T type:

    
    
    
        
with Ada.Text_IO; use Ada.Text_IO; with System; with Custom_Types; use Custom_Types; procedure Show_Sizes is begin Put_Line ("T'Size: " & Integer'Image (T'Size / System.Storage_Unit) & " storage elements (" & T'Size'Image & " bits)"); Put_Line ("T'Max_Size_In_Storage_Elements: " & T'Max_Size_In_Storage_Elements'Image & " storage elements (" & Integer'Image (T'Max_Size_In_Storage_Elements * System.Storage_Unit) & " bits)"); Put_Line ("T'Max_Alignment_For_Allocation: " & T'Max_Alignment_For_Allocation'Image & " storage elements (" & Integer'Image (T'Max_Alignment_For_Allocation * System.Storage_Unit) & " bits)"); end Show_Sizes;

On a typical desktop PC, you might get 4 storage elements (corresponding to 32 bits) as the value returned by these attributes.

In the original implementation of the Custom_Types package, we allowed the compiler to select the size of type T. We can be more specific in the type declarations and use the Size aspect for that type:

    
    
    
        
package Custom_Types is type T is new Integer with Size => 48; type T_Array is array (Positive range <>) of T; type T_Access is access T; end Custom_Types;

Let's see how this change affects the Size, Max_Size_In_Storage_Elements, and Max_Alignment_For_Allocation attributes:

    
    
    
        
with Ada.Text_IO; use Ada.Text_IO; with System; with Custom_Types; use Custom_Types; procedure Show_Sizes is begin Put_Line ("T'Size: " & Integer'Image (T'Size / System.Storage_Unit) & " storage elements (" & T'Size'Image & " bits)"); Put_Line ("T'Max_Size_In_Storage_Elements: " & T'Max_Size_In_Storage_Elements'Image & " storage elements (" & Integer'Image (T'Max_Size_In_Storage_Elements * System.Storage_Unit) & " bits)"); Put_Line ("T'Max_Alignment_For_Allocation: " & T'Max_Alignment_For_Allocation'Image & " storage elements (" & Integer'Image (T'Max_Alignment_For_Allocation * System.Storage_Unit) & " bits)"); end Show_Sizes;

If the code compiles, you should see that T'Size now corresponds to 6 storage elements (i.e. 48 bits). On a typical desktop PC, the value of T'Max_Size_In_Storage_Elements and T'Max_Alignment_For_Allocation should have increased to 8 storage elements (64 bits).

Code example with array type

Note that using the Size and Max_Size_In_Storage_Elements attributes on array types can give you a potentially higher number:

    
    
    
        
with Ada.Text_IO; use Ada.Text_IO; with System; with Custom_Types; use Custom_Types; procedure Show_Sizes is begin Put_Line ("T_Array'Max_Size_In_Storage_Elements: " & T_Array'Max_Size_In_Storage_Elements'Image & " storage elements (" & Long_Integer'Image (T_Array'Max_Size_In_Storage_Elements * System.Storage_Unit) & " bits)"); Put_Line ("T_Array'Max_Alignment_For_Allocation: " & T_Array'Max_Alignment_For_Allocation'Image & " storage elements (" & Integer'Image (T_Array'Max_Alignment_For_Allocation * System.Storage_Unit) & " bits)"); Put_Line ("T_Array'Size: " & Long_Integer'Image (T_Array'Size / System.Storage_Unit) & " storage elements (" & T_Array'Size'Image & " bits)"); end Show_Sizes;

In this case, these values indicate the maximum amount of memory that is theoretically available for the array in the memory pool. This information allows us to calculate the (theoretical) maximum number of components for an array of this type:

    
    
    
        
with Ada.Text_IO; use Ada.Text_IO; with System; with Custom_Types; use Custom_Types; procedure Show_Sizes is begin Put_Line ("T_Array: Max. number of components: " & Long_Integer'Image (T_Array'Max_Size_In_Storage_Elements / (T'Size / System.Storage_Unit)) & " components"); end Show_Sizes;

By dividing the value returned by the Max_Size_In_Storage_Elements attribute with the size of each individual component, we can get the maximum number of components.

Storage elements

We saw parts of the System.Storage_Elements package while discussing addresses. However, we haven't discussed yet the main types from that package: Storage_Element and Storage_Array.

We defined storage elements previously. In the System.Storage_Elements package, a storage element is represented by the Storage_Element type. Its size (Storage_Element'Size) is equal to Storage_Unit — which we also mentioned previously.

The Storage_Array type is an array type of storage elements. This is its definition:

type Storage_Array is
  array (Storage_Offset range <>) of
    aliased Storage_Element;

A storage array is used to represent a contiguous sequence of storage elements in memory. In other words, you can think of an object of Storage_Array type as a (memory) buffer.

Important

Note that arrays of Storage_Array type are guaranteed by the language to be contiguous. In contrast, storage pools are not required to be contiguous blocks of memory. However, each memory allocation in a storage pool returns a pointer to a contiguous block of memory.

Also, arrays in general are not guaranteed to be contiguous — apart from arrays of Storage_Array type, as we've just seen. In practice, however, if you're using a modern architecture, you most likely won't encounter an array that isn't allocated on a contiguous block. (You would perhaps see an array allocated on non-contiguous blocks when using an older architecture with segmented memory.)

For further reading

Note that the Storage_Offset is an integer type with a range defined by the compiler implementation. It's used not only in the definition of the Storage_Array but also in address arithmetic, which we discussed in an earlier chapter.

In fact, the Storage_Array is used in the generic Storage_IO package to define a memory buffer:

with System.Storage_Elements;
use  System.Storage_Elements;

subtype Buffer_Type is
  Storage_Array (1 .. Buffer_Size);

Let's see a simple example that makes use of the Storage_IO package:

    
    
    
        
pragma Ada_2022; with Ada.Text_IO; use Ada.Text_IO; with Ada.Storage_IO; procedure Show_Storage_IO is type Rec is record A, B : Integer; C : Float; end record; package Rec_Storage_IO is new Ada.Storage_IO (Element_Type => Rec); use Rec_Storage_IO; Buf : Buffer_Type; R1, R2 : Rec; begin R1 := (1, 2, 3.0); Put_Line ("R1 : " & R1'Image); -- Writing from R1 to the buffer Buf: Write (Buf, R1); -- Reading from the buffer Buf to R2: Read (Buf, R2); Put_Line ("R2 : " & R2'Image); end Show_Storage_IO;

In this example, we instantiate the Storage_IO package for the Rec type and declare a buffer Buf of Buffer_Type type. (Note that Buf is essentially an array of Storage_Array type.) We then use this buffer and write an element to it (via Write) and read from it (via Read).

Memory pools

Todo

Complete section!

Memory subpools

Todo

Complete section!

Secondary stack

Relevant topics

  • GNAT-specific secondary stack

Todo

Complete section!