Ada Programming/Types/access
What's an Access Type?
[edit | edit source]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 Ada.Containers.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 access
[edit | edit source]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
Personis
record
First_Name : String (1..30); Last_Name : String (1..20);end
record
;type
Person_Accessis
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.
for
Person_Access'Storage_Sizeuse
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_Pooluse
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.
is the complete record; components are denoted as usual with the dot notation: all
Mother.
. When accessing components, implicit dereferencing (i.e. omitting all
.First_Nameall
) 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
Vectoris
array
(1 .. 3)of
Complex;type
Vector_Accessis
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 pool
[edit | edit source]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
[edit | edit source]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
[edit | edit source]The following program will compile but will fail the accessibility check on runtime with an exception.
with
Ada.Text_IO;use
Ada.Text_IO;procedure
Mainis
function
Accessibility_Check_Failreturn
access
Stringis
-- 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.type
A_Typeis
access
String; -- no Storage_Size defined X : A_Type :=new
String'("x"); -- storage will be lost Y :access
String; -- defined locallybegin
Y := X; -- data defined in a local pool will be finalized when function returnsreturn
Y; -- exception should be raisedend
Accessibility_Check_Fail;begin
-- 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.all
);end
Main;
There is also a Note that
, which, when applied to such an access type, prevents automatic garbage collection of objects created with it.pragma
Controlled
was dropped from Ada 2012, subpools for storage management replacing it. See RM 2012 13.11.3 [Annotated] and 13.11.4 [Annotated].
pragma
Controlled
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_Sampleis
type
Vectoris
array
(Integerrange
<>)of
Float;type
Vector_Refis
access
Vector;procedure
Free_Vectoris
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 erroneousend
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
[edit | edit source]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
[edit | edit source]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 Variable
[edit | edit source]When the keyword all
is used in their definition, they grant read-write access.
type
Day_Of_Monthis
range
1 .. 31;type
Day_Of_Month_Accessis
access
all
Day_Of_Month;
Access to Constant
[edit | edit source]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_Monthis
range
1 .. 31;type
Day_Of_Month_Accessis
access
constant
Day_Of_Month;
Some examples
[edit | edit source]type
General_Pointeris
access
all
Integer;type
Constant_Pointeris
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 access
[edit | edit source]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
-- componentsend
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 from Ada 2005 on.
In Ada 2005, anonymous accesses are allowed in more circumstances:
type
Objectis
record
M : Integer; Next:access
Object;end
record
; X:access
Integer;function
Freturn
access
constant
Float;
Implicit Dereference
[edit | edit source]This language feature has been introduced in Ada 2012.
Ada 2012 simplifies accesses to objects via pointers with new syntax.
Imagine you have a container holding some kind of elements.
type
Containeris
private
;type
Element_Ptris
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 inadvertent deallocation (this is still Ada 2005):
type
Accessor (Data:not
null
access
Element)is
limited
private
; -- read/write accessfunction
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 exclusions
[edit | edit source]This language feature is only available from Ada 2005 on.
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_Accessis
access
Day_Of_Month;subtype
Day_Of_Month_Not_Null_Accessis
not
null
Day_Of_Month_Access;
The language also allows to declare the first subtype directly with a null exclusion:
type
Day_Of_Month_Accessis
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 Subprogram
[edit | edit source]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.
type
Callback_Procedureis
access
procedure
(Id : Integer; Text: String);type
Callback_Functionis
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 Subprogram
[edit | edit source]This language feature is only available from Ada 2005 on.
procedure
Test (Call_Back:access
procedure
(Id: Integer; Text: String));
There is now no limit on the number of keyword in a sequence:
function
Freturn
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 FAQ
[edit | edit source]A few "Frequently Asked Question" and "Frequently Encountered Problems" (mostly from C users) regarding Ada's access types.
Access vs. access all
[edit | edit source]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_Monthis
range
1 .. 31;type
Day_Of_Month_Accessis
access
all
Day_Of_Month;procedure
Freeis
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. The reason is that access
does not allow pointers to be created from stack objects.
Access vs. System.Address
[edit | edit source]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
[edit | edit source]The correct way to create a C compatible access is to use pragma
Convention:
type
Day_Of_Monthis
range
1 .. 31;for
Day_Of_Month'Sizeuse
Interfaces.C.int'Size;pragma
Convention (Convention => C, Entity => Day_Of_Month);type
Day_Of_Month_Accessis
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_Monthis
new
Interfaces.C.intrange
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 | edit source]While actually a problem for "interfacing with C", here are some possible solutions:
procedure
Testis
subtype
Pvoidis
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
voidis
mod
2 ** 32;for
void'Sizeuse
32;
With GNAT you can get 32/64 bit portability by using:
type
voidis
mod
System.Memory_Size;for
void'Sizeuse
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
Voidis
null
record
;pragma
Convention (C, Void);type
Void_Ptris
access
all
Void;pragma
Convention (C, Void_Ptr);
Thin and Fat Access Types
[edit | edit source]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 Pointers
[edit | edit source]Thin pointers grant access to constrained subtypes.
type
Intis
range
-100 .. +500;type
Acc_Intis
access
Int;type
Arris
array
(1 .. 80)of
Character;type
Acc_Arris
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 Pointers
[edit | edit source]type
Uncis
array
(Integerrange
<>)of
Character;type
Acc_Uncis
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.
Example
[edit | edit source]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 Acc_Unc
'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 also
[edit | edit source]Wikibook
[edit | edit source]Ada Reference Manual
[edit | edit source]Ada 95
[edit | edit source]- 4.8: Allocators [Annotated]
- 13.11: Storage Management [Annotated]
- 13.11.2: Unchecked Storage Deallocation [Annotated]
- 3.7: Discriminants [Annotated]
- 3.10: Access Types [Annotated]
- 6.1: Subprogram Declarations [Annotated]
- B.3: Interfacing with C [Annotated]
Ada 2005
[edit | edit source]- 4.8: Allocators [Annotated]
- 13.11: Storage Management [Annotated]
- 13.11.2: Unchecked Storage Deallocation [Annotated]
- 3.7: Discriminants [Annotated]
- 3.10: Access Types [Annotated]
- 6.1: Subprogram Declarations [Annotated]
- B.3: Interfacing with C [Annotated]
Newest RM
[edit | edit source]- 3.10: Access Types [Annotated]
- 7.6: Assignment and Finalization [Annotated]
- 7.6.1: Completion and Finalization [Annotated]
- 13.11: Storage Management [Annotated]
Ada Quality and Style Guide
[edit | edit source]References
[edit | edit source]