Ada Programming/Types/access

What's an Access Type?
Access types in Ada are what other languages call pointers. They point to objects located at certain addresses. So normally one can think of access types as simple addresses (there are exceptions from this simplified view). Ada instead of saying points to talks of granting access to or designating an object.

Objects of access types are implicitly initialized with, i.e. they point to nothing when not explicitly initialized.

Access types should be used rarely in Ada. In a lot of circumstances where pointers are used in other languages, there are other ways without pointers. If you need dynamic data structures, first check whether you can use the Ada Container library. Especially for indefinite record or array components, the Ada 2012 package (RM ) can be used instead of pointers.

There are four kinds of access types in Ada: Pool access types - General access types - Anonymous access types - Access to subprogram types.

Pool access
A pool access type handles accesses to objects which were created on some specific heap (or storage pool as it is called in Ada). A pointer of these types cannot point to a stack or library level (static) object or an object in a different storage pool. Therefore, conversion between pool access types is illegal. (Unchecked_Conversion may be used, but note that deallocation via an access object with a storage pool different from the one it was allocated with is erroneous.)

Person First_Name : String (1..30); Last_Name : String (1..20); ; Person_Access   Person;

A storage size clause may be used to limit the corresponding (implementation defined anonymous) storage pool. A storage size clause of 0 disables calls of an allocator.

Person_Access' 0;

The storage pool is implementation defined if not specified. Ada supports user defined storage pools, so you can define the storage pool with

Person_Access' Pool_Name;

Objects in a storage pool are created with the keyword :

Father: Person_Access := Person;                                          -- uninitialized Mother: Person_Access := Person'(Mothers_First_Name, Mothers_Last_Name);  -- initialized

You access the object in the storage pool by appending. is the complete record; components are denoted as usual with the dot notation:. When accessing components, implicit dereferencing (i.e. omitting ) can serve as a convenient shorthand:

Mother. := (Last_Name => Father.Last_Name, First_Name => Mother.First_Name); -- marriage

Implicit dereferencing also applies to arrays:

Vector  (1 .. 3)  Complex; Vector_Access  Vector; VA: Vector_Access := Vector; VB: (1 .. 3)  Vector_Access := ( =>  Vector); C1: Complex := VA (3);   -- a shorter equivalent for VA. (3)  C2: Complex := VB (3)(1); -- a shorter equivalent for VB(3). (1)

Be careful to discriminate between deep and shallow copies when copying with access objects:

Obj1. := Obj2.; -- Deep copy: Obj1 still refers to an object different from Obj2, but it has the same content Obj1 := Obj2;         -- Shallow copy: Obj1 now refers to the same object as Obj2

Deleting objects from a storage pool
Although the Ada standard mentions a garbage collector, which would automatically remove all unneeded objects that have been created on the heap (when no storage pool has been defined), only Ada compilers targeting a virtual machine like Java or .NET actually have garbage collectors.

When an access type goes out of scope, the corresponding still allocated data items are finalized (i.e. they do no longer exist) in an arbitrary order; the allocated memory, however, is only freed when the attribute Storage_Size has been defined for the access type via an attribute_definition clause. (Note: Finalization and deallocation are different things!)

Proof
The following are excerpts from the Ada Reference Manual. The ellipses stand for parts not relevant for the case.

RM 3.10(7/1) There are ... access-to-object types, whose values designate objects... Associated with an access-to-object type is a storage pool; several access types may share the same storage pool. ... A storage pool is an area of storage used to hold dynamically allocated objects (called pool elements) created by allocators.

(8) Access-to-object types are further subdivided into pool-specific access types, whose values can designate only the elements of their associated storage pool...

RM 7.6(1) ... Every object is finalized before being destroyed (for example, by leaving a subprogram_body containing an object_declaration, or by a call to an instance of Unchecked_Deallocation)...

RM 7.6.1(5) For the finalization of an object:

(6/3) If the full type of the object is an elementary type, finalization has no effect;

(7/3) If the full type of the object is a tagged type, and the tag of the object identifies a controlled type, the Finalize procedure of that controlled type is called;

(10) Immediately before an instance of Unchecked_Deallocation reclaims the storage of an object, the object is finalized. If an instance of Unchecked_Deallocation is never applied to an object created by an allocator, the object will still exist when the corresponding master completes, and it will be finalized then.

(11.1/3) Each nonderived access type T has an associated collection, which is the set of objects created by allocators of T, or of types derived from T. Unchecked_Deallocation removes an object from its collection. Finalization of a collection consists of finalization of each object in the collection, in an arbitrary order…

RM 13.11(1) Each access-to-object type has an associated storage pool. The storage allocated by an allocator comes from the pool; instances of Unchecked_Deallocation return storage to the pool. Several access types can share the same pool.

(2/2) A storage pool is a variable of a type in the class rooted at Root_Storage_Pool, which is an abstract limited controlled type. By default, the implementation chooses a standard storage pool for each access-to-object type…

(11) A storage pool type (or pool type) is a descendant of Root_Storage_Pool. The elements of a storage pool are the objects allocated in the pool by allocators.

(15) Storage_Size or Storage_Pool may be specified for a nonderived access-to-object type via an attribute_definition_clause...

(17) If Storage_Pool is not specified for a type defined by an access_to_object_definition, then the implementation chooses a standard storage pool for it in an implementation-defined manner...

(18/4) If Storage_Size is specified for an access type T, an implementation-defined pool P is used for the type. The Storage_Size of P is at least that requested, and the storage for P is reclaimed when the master containing the declaration of the access type is left...

Example
The following program will compile but will fail the accessibility check on runtime with an exception.

Ada.Text_IO; Ada.Text_IO; Main Accessibility_Check_Fail String -- Declare a new access type locally. -- All memory with this type will be finalized but not freed -- when the this type goes out of scope. A_Type  String; X : A_Type := String'("x"); Y : String; Y := X;         Y;       Accessibility_Check_Fail; -- Accessibility check will fail because the accessiblity level associated -- with Y is deeper than the accessibility level of this scope. Put_Line(Accessibility_Check_Fail.); Main;

There is also a, which, when applied to such an access type, prevents automatic garbage collection of objects created with it. Note that  was dropped from Ada 2012, subpools for storage management replacing it. See RM 2012 and.

Therefore, in order to delete an object from the heap, you need the generic unit. Apply utmost care to not create dangling pointers when deallocating objects as is shown in the example below. (And note that deallocating objects with a different access type than the one with which they were created is erroneous when the corresponding storage pools are different.)

; Deallocation_Sample Vector      (Integer  <>)  Float; Vector_Ref  Vector; Free_Vector (Object => Vector, Name => Vector_Ref); VA, VB: Vector_Ref; V    : Vector; VA    :=  Vector (1 .. 10); VB    := VA;  -- points to the same location as VA    VA. := ( => 0.0); -- ... Do whatever you need to do with the vector Free_Vector (VA); -- The memory is deallocated and VA is now null V := VB.all; -- VB is not null, access to a dangling pointer is erroneous Deallocation_Sample;

It is exactly because of this problem with dangling pointers that the deallocation operation is called unchecked. It is the chore of the programmer to take care that this does not happen.

Since Ada allows for user-defined storage pools, you could also try a garbage collector library.

Constructing Reference Counting Pointers
You can find some implementations of reference counting pointers, called Safe or Smart Pointers, on the net. Using such a type prevents caring about deallocation, since this will automatically be done when there are no more pointers to an object. But be careful - most of those implementations do not prevent deliberate deallocation, thus undermining the alleged safety attained with their use.

A nice tutorial how to construct such a type can be found in a series of Gems on the AdaCore web site.

Gem #97: Reference Counting in Ada – Part 1 This little gem constructs a simple reference counted pointer that does not prevent deallocation, i.e. is inherently unsafe.

Gem #107: Preventing Deallocation for Reference-counted Types This further gem describes how to arrive at a pointer type whose safety cannot be compromised (tasking issues aside). The cost of this improved safety is awkward syntax.

Gem #123: Implicit Dereferencing in Ada 2012 This gem shows how to simplify the syntax with the new Ada 2012 generation. (Admittedly, this gem is a bit unrelated to reference counting since the new language feature can be applied to any kind of container.)

General access
General access types grant access to objects created on any storage pool, on the stack or at library level (static). They come in two versions, granting either read-write access or read-only access. Conversions between general access types are allowed, but subject to certain access level checks.

Dereferencing is like for pool access types. Objects (other than pool objects) to be referenced have to be declared, and references to them are created with the attribute. Access level restrictions prevent accesses to objects from outliving the accessed object, which would make the program erroneous. The attribute  omits the corresponding checks.

Access to Variable
When the keyword is used in their definition, they grant read-write access.

Day_Of_Month  1 .. 31;             Day_Of_Month_Access    Day_Of_Month;

Access to Constant
General access types granting read-only access to the referenced object use the keyword in their definition. The referenced object may be a constant or a variable.

Day_Of_Month  1 .. 31;             Day_Of_Month_Access    Day_Of_Month;

Some examples
General_Pointer         Integer; Constant_Pointer   Integer; I1:  Integer := 10; I2: Integer; P1: General_Pointer := I1';  -- illegal P2: Constant_Pointer := I1'; -- OK, read only P3: General_Pointer := I2';  -- OK, read and write P4: Constant_Pointer := I2'; -- OK, read only P5: General_Pointer := I2'Access;  -- read and write only to I2

Anonymous access
Also Anonymous access types come in two versions like general access types, granting either read-write access or read-only access depending on whether the keyword appears.

An anonymous access can be used as a parameter to a subprogram or as a discriminant. Here are some examples:

Modify (Some_Day:          Day_Of_Month); Test  (Some_Day:   Day_Of_Month);  -- Ada 2005 only

Thread (Execute_For_Day: Day_Of_Month) ... Thread;

Day_Data (Store_For_Day: Day_Of_Month) -- components ;

Before using an anonymous access, you should consider a named access type or, even better, consider if the "" or " " modifier is not more appropriate.

In Ada 2005, anonymous accesses are allowed in more circumstances:

Object M  : Integer; Next: Object; ; X: Integer; F   Float;

Implicit Dereference
Ada 2012 simplifies accesses to objects via pointers with new syntax.

Imagine you have a container holding some kind of elements.

Container   ; Element_Ptr  Element; Put (X: Element; Into:  Container);

Now, how do you access elements stored in the container. Of course, you can retrieve them by

Get (From: Container) Element;

This will however copy the element, which is unfortunate if the element is big. You get direct access with

Get (From: Container) Element_Ptr;

Now, pointers are dangerous since you might easily create dangling pointers like so:

P: Element_Ptr := Get (Cont); P. := E; Free (P); ... Get (Cont) -- this is now a dangling pointer

Use of an accessor object instead of an access type can prevent inadvertent deallocation (this is still Ada 2005):

Accessor (Data:   Element)   ;  -- read/write access Get (From: Container) Accessor;

(For the null exclusion  in the declaration of the discriminant, see below). Access via such an accessor is safe: The discriminant can only be used for dereferencing, it cannot be copied to an object of type Element_Ptr because its accessibility level is deeper. In the form above, the accessor provides read and write access. If the keyword is added, only read access is possible.

Accessor (Data:    Element)   ;  -- only read access

Access to the container object now looks like so:

Get (Cont). := E; -- via access type: dangerous Get (Cont).Data. := E; -- via accessor: safe, but ugly

Here the new Ada 2012 feature of aspects comes along handy; for the case at hand, the aspect Implicit_Dereference is the one we need:

Accessor (Data:   Element) Implicit_Dereference => Data;

Now rather than writing the long and ugly function call of above, we can just omit the discriminant and its dereference like so:

Get (Cont).Data. := E; -- Ada 2005 via accessor: safe, but ugly Get (Cont)         := E;  -- Ada 2012 implicit dereference

Note that the call  is overloaded — it can denote the accessor object or the element, the compiler will select the correct interpretation depending on context.

Null exclusions
All access subtypes can be modified with, objects of such a subtype can then never have the value null, so initializations are compulsory.

Day_Of_Month_Access             Day_Of_Month; Day_Of_Month_Not_Null_Access   Day_Of_Month_Access;

The language also allows to declare the first subtype directly with a null exclusion:

Day_Of_Month_Access    Day_Of_Month;

However, in nearly all cases this is not a good idea because it renders objects of this type nearly unusable (for example, you are unable to free the allocated memory). Not null accesses are intended for access subtypes, object declarations, and subprogram parameters.

Access to Subprogram
An access to subprogram allows the caller to call a subprogram without knowing its name nor its declaration location. One of the uses of this kind of access is the well known callbacks.

Callback_Procedure   (Id  : Integer;                                              Text: String); Callback_Function   (The_Alarm: Alarm)  Natural;

For getting an access to a subprogram, the attribute is applied to a subprogram name with the proper parameter and result profile.

Process_Event (Id : Integer;                          Text: String); My_Callback: Callback_Procedure := Process_Event';

Anonymous access to Subprogram
Test (Call_Back:  (Id: Integer; Text: String));

There is now no limit on the number of keyword in a sequence:

F        Some_Type;

This is a function that returns the access to a function that in turn returns an access to a function returning an access to some type.

Access FAQ
A few "Frequently Asked Question" and "Frequently Encountered Problems" (mostly from C users) regarding Ada's access types.

Access vs. access all
An  can do anything a simple  can do. So one might ask: "Why use simple at all?" - And indeed some programmers never use simple.

Unchecked_Deallocation is always dangerous if misused. It is just as easy to deallocate a pool-specific object twice, and just as dangerous as deallocating a stack object. The advantage of "access all" is that you may not need to use Unchecked_Deallocation at all.

Moral: if you have (or may have) a valid reason to store an 'Access or 'Unchecked_Access into an access object, then use "access all" and don't worry about it. If not, the mantra of "least privilege" suggests that the "all" should be left out (don't enable capabilities that you are not going to use).

The following (perhaps disastrous) example will try to deallocate a stack object:

Day_Of_Month  1 .. 31;               Day_Of_Month_Access    Day_Of_Month; Free  Ada.Unchecked_Deallocation (Object => Day_Of_Month,       Name   => Day_Of_Month_Access); A :  Day_Of_Month; Ptr: Day_Of_Month_Access := A'; Free(Ptr); ;

With a simple you know at least that you won't try to deallocate a stack object. The reason is that  does not allow pointers to be created from stack objects.

Access vs. System.Address
An access can be something different from a mere memory address, it may be something more. For example, an "access to String" often needs some way of storing the string size as well. If you need a simple address and are not concerned about strong typing, use the System.Address type.

C compatible pointer
The correct way to create a C compatible access is to use :

Day_Of_Month  1 .. 31;  Day_Of_Month'  Interfaces.C.int'; (Convention => C,                   Entity     => Day_Of_Month); Day_Of_Month_Access  Day_Of_Month; (Convention => C,                   Entity     => Day_Of_Month_Access);

should be used on any type you want to use in C. The compiler will warn you if the type cannot be made C compatible.

You may also consider the following - shorter - alternative when declaring Day_Of_Month:

Day_Of_Month  Interfaces.C.int  1 .. 31;

Before you use access types in C, you should consider using the normal "in", "out" and "in out" modifiers. and know how parameters are usually passed in C and will use a pointer to pass a parameter automatically where C would have used them as well. Of course the RM contains precise rules on when to use a pointer for "in", "out", and "in out" - see "".

Where is void*?
While actually a problem for "interfacing with C", here are some possible solutions:

Test Pvoid System.Address; -- the declaration in C looks like this: -- int C_fun(int *) C_fun (pv: Pvoid) Integer; (Convention   => C,                  Entity        => C_fun,     -- any Ada name                  External_Name => "C_fun");  -- the C name Pointer: Pvoid; Input_Parameter: Integer := 32; Return_Value  : Integer; Pointer     := Input_Parameter'; Return_Value := C_fun (Pointer); Test;

Less portable, but perhaps more usable (for 32 bit CPUs) :

void  2 ** 32; void' 32;

With GNAT you can get 32/64 bit portability by using:

void  System.Memory_Size; void' System.Word_Size;

Closer to the true nature of void - pointing to an element of zero size is a pointer to a null record. This also has the advantage of having a representation for  and  :

Void  ; (C, Void); Void_Ptr   Void; (C, Void_Ptr);

Thin and Fat Access Types
The difference between an access type and an address will be detailed in the following. The term pointer is used because this is usual terminology.

There is a predefined unit  converting back and forth between access values and addresses. Use these conversions with care, as is explained below.

Thin Pointers
Thin pointers grant access to constrained subtypes.

Int      -100 .. +500; Acc_Int   Int; Arr      (1 .. 80)  Character; Acc_Arr  Arr;

Objects of subtypes like these have a static size, so a simple address suffices to access them. In the case of arrays, this is generally the address of the first element.

For pointers of this kind, use of  is safe.

Fat Pointers
Unc      (Integer  <>)  Character; Acc_Unc  Unc;

Objects of subtype  need a constraint, i.e. a start and a stop index, thus pointers to them need also to include those. So a simple address like the one of the first component is not sufficient. Note that A'Address is the same as A(A'First)'Address for any array object.

For pointers of this kind,  will probably not work satisfactorily.

Example
CO: Unc (-1 .. +1) := (-1 .. +1 => ' '); UO: Unc            := (-1 .. +1 => ' ');

Here, CO is a nominally constrained object, a pointer to it need not store the constraint, i.e. a thin pointer suffices. In contrast, UO is an object of a nominally unconstrained subtype, its actual subtype is constrained by the initial value.

A: Acc_Unc           := CO'Access;  -- illegal B: Acc_Unc           := UO'Access;  -- OK C: Acc_Unc (CO'Range) := CO'Access;  -- also illegal

The relevant paragraphs in the RM are difficult to understand. In short words:

An access type's target type is called the designated subtype, in our example. RM (27.1/2) requires that 's designated subtype statically match the nominal subtype of the object.

Now the nominal subtype of  is the constrained anonymous subtype , the nominal subtype of   is the unconstrained subtype. In the illegal cases, the designated and nominal subtypes do not statically match.

Wikibook

 * Ada Programming
 * Ada Programming/Types