Last modified on 31 May 2013, at 12:12

Ada Programming/Types/access


Ada Lovelace 1838.jpg

What's an Access Type?Edit

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 null, 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 Indefinite_Holders (RM A.18.18 (Annotated)) 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 accessEdit

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.)

type Person is record
  First_Name : String (1..30);
  Last_Name  : String (1..20);
end record;

type Person_Access is access Person;

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

for Person_Access'Size use 0;

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

for Person_Access'Storage_Pool use Pool_Name;

Objects in a storage pool are created with the keyword new:

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

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

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

Implicit dereferencing also applies to arrays:

  type Vector is array (1 .. 3) of Complex;
  type Vector_Access is access Vector;

  VA: Vector_Access := new Vector;
  VB: array (1 .. 3) of Vector_Access := (others => new Vector);

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

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

Obj1.all := Obj2.all;  -- 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 poolEdit

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. There is also a pragma Controlled, which, when applied to such an access type, prevents automatic garbage collection of objects created with it. Note that pragma Controlled will be dropped from Ada 2012, subpools for storage management replacing it. See RM 2012 13.11.3 (Annotated) and 13.11.4 (Annotated).

Therefore in order to delete an object from the heap, you need the generic unit Ada.Unchecked_Deallocation. 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.)

with Ada.Unchecked_Deallocation;

procedure Deallocation_Sample is

   type Vector     is array (Integer range <>) of Float;
   type Vector_Ref is access Vector;

   procedure Free_Vector is new Ada.Unchecked_Deallocation
      (Object => Vector, Name => Vector_Ref);
  
   VA, VB: Vector_Ref;
   V     : Vector;

begin

   VA     := new Vector (1 .. 10);
   VB     := VA;  -- points to the same location as VA

   VA.all := (others => 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

end 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 PointersEdit

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 alledged 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 accessEdit

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 aliased, and references to them are created with the attribute 'Access. Access level restrictions prevent accesses to objects from outliving the accessed object, which would make the program erroneous. The attribute 'Unchecked_Access omits the corresponding checks.

Access to VariableEdit

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

type Day_Of_Month is range 1 .. 31;            
type Day_Of_Month_Access is access all Day_Of_Month;

Access to ConstantEdit

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

type Day_Of_Month is range 1 .. 31;            
type Day_Of_Month_Access is access constant Day_Of_Month;

Some examplesEdit

 type General_Pointer  is access all      Integer;
 type Constant_Pointer is access constant Integer;

 I1: aliased constant Integer := 10;
 I2: aliased Integer;

 P1: General_Pointer  := I1'Access;  -- illegal
 P2: Constant_Pointer := I1'Access;  -- OK, read only
 P3: General_Pointer  := I2'Access;  -- OK, read and write
 P4: Constant_Pointer := I2'Access;  -- OK, read only

 P5: constant General_Pointer := I2'Access;  -- read and write only to I2

Anonymous accessEdit

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 constant appears.

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

procedure Modify (Some_Day: access          Day_Of_Month);
procedure Test   (Some_Day: access constant Day_Of_Month);  -- Ada 2005 only
task type Thread (Execute_For_Day: access Day_Of_Month) is
   ...
end Thread;
type Day_Data (Store_For_Day: access Day_Of_Month) is record
   --  components
end record;

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

This language feature is only available in Ada 2005.

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

type Object is record
  M   : Integer;
  Next: access Object;
end record;

X: access Integer;

function F return access constant Float;

Implicit DereferenceEdit

Ada 2012 will simplify accesses to objects via pointers with new syntax.

Imagine you have a container holding some kind of elements.

type Container   is private;
type Element_Ptr is access Element;

procedure Put (X: Element; Into: in out Container);

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

function Get (From: Container) return Element;

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

function Get (From: Container) return Element_Ptr;

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

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

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

type Accessor (Data: not null access Element) is limited private;  -- read/write access
function Get (From: Container) return Accessor;

(For the null exclusion not null 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 constant is added, only read access is possible.

type Accessor (Data: not null access constant Element) is limited private;  -- only read access

Access to the container object now looks like so:

Get (Cont).all      := E;  -- via access type: dangerous
Get (Cont).Data.all := 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:

type Accessor (Data: not null access Element) is limited private
   with 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.all := E;  -- Ada 2005 via accessor: safe, but ugly
Get (Cont)          := E;  -- Ada 2012 implicit dereference

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

Null exclusionsEdit

This language feature is only available in Ada 2005.

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

type    Day_Of_Month_Access          is access   Day_Of_Month;
subtype Day_Of_Month_Not_Null_Access is not null Day_Of_Month_Access;

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

type Day_Of_Month_Access is not null 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.[1]

Access to SubprogramEdit

An access to subprogram allows 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.

type Callback_Procedure is access procedure (Id  : Integer;
                                             Text: String);

type Callback_Function is access function (The_Alarm: Alarm) return Natural;

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

procedure Process_Event (Id  : Integer;
                         Text: String);

My_Callback: Callback_Procedure := Process_Event'Access;

Anonymous access to SubprogramEdit

This language feature is only available in Ada 2005.

procedure Test (Call_Back: access procedure (Id: Integer; Text: String));

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

function F return access function return access function return access 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 FAQEdit

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

Access vs. access allEdit

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

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:

declare

  type Day_Of_Month is range 1 .. 31;            
  type Day_Of_Month_Access is access all Day_Of_Month;

  procedure Free is new Ada.Unchecked_Deallocation
      (Object => Day_Of_Month
       Name   => Day_Of_Month_Access);

  A  : aliased Day_Of_Month;
  Ptr: Day_Of_Month_Access := A'Access;

begin

   Free(Ptr);

end;

With a simple access you know at least that you won't try to deallocate a stack object.

Access vs. System.AddressEdit

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 pointerEdit

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

type Day_Of_Month is range 1 .. 31;
for  Day_Of_Month'Size use Interfaces.C.int'Size;

pragma Convention (Convention => C,
                   Entity     => Day_Of_Month);

type Day_Of_Month_Access is access Day_Of_Month;

pragma Convention (Convention => C,
                   Entity     => Day_Of_Month_Access);

pragma Convention 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:

type Day_Of_Month is new Interfaces.C.int range 1 .. 31;

Before you use access types in C, you should consider using the normal "in", "out" and "in out" modifiers. pragma Export and pragma Import 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 "B.3: Interfacing with C (Annotated)".

Where is void*?Edit

While actually a problem for "interfacing with C", here are some possible solutions:

procedure Test is

  subtype Pvoid is System.Address;

  -- the declaration in C looks like this:
  -- int C_fun(int *)
  function C_fun (pv: Pvoid) return Integer;
  pragma Import (Convention    => C,
                 Entity        => C_fun,     -- any Ada name
                 External_Name => "C_fun");  -- the C name

  Pointer: Pvoid;

  Input_Parameter: aliased Integer := 32;
  Return_Value   : Integer;

begin

  Pointer      := Input_Parameter'Address;
  Return_Value := C_fun (Pointer);

end Test;

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

type void is mod 2 ** 32;
for void'Size use 32;

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

type void is mod System.Memory_Size;
for void'Size use 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 void and void*:

type Void is null record;
pragma Convention (C, Void);

type Void_Ptr is access all Void;
pragma Convention (C, Void_Ptr);

Thin and Fat Access TypesEdit

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 System.Address_to_Access_Conversion converting back and forth between access values and addresses. Use these conversions with care, as is explained below.

Thin PointersEdit

Thin pointers grant access to constrained subtypes.

type Int     is range -100 .. +500;
type Acc_Int is access Int;

type Arr     is array (1 .. 80) of Character;
type Acc_Arr is access 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 System.Address_to_Access_Conversion is safe.

Fat PointersEdit

type Unc     is array (Integer range <>) of Character;
type Acc_Unc is access Unc;

Objects of subtype Unc 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, System.Address_to_Access_Conversion will probably not work satisfactorily.

ExampleEdit

CO: aliased Unc (-1 .. +1) := (-1 .. +1 => ' ');
UO: aliased 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 Unc. RM 3.10.2 (Annotated)(27.1/2) requires that Unc_Acc's designated subtype statically match the nominal subtype of the object.

Now the nominal subtype of CO is the constrained anonymous subtype Unc (-1 .. +1), the nominal subtype of UO is the unconstrained subtype Unc. In the illegal cases, the designated and nominal subtypes do not statically match.

See alsoEdit

WikibookEdit

Ada Reference ManualEdit

Ada 95Edit

Ada 2005Edit

Ada Quality and Style GuideEdit