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.
Relevant topics
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
Relevant topics
Todo
Complete section!
Memory subpools
Relevant topics
Todo
Complete section!
Secondary stack
Relevant topics
GNAT-specific secondary stack
Todo
Complete section!