Ada Programming/Types/access
From Wikibooks, the open-content textbooks collection
Contents |
[edit] Thin and Fat Access Types
Normally one thinks of access types as simple addresses, i.e. what pointers are in some other languages. Ada uses a different name because objects of this category do not really point to objects, they grant access to them. This will be detailed in the following. The term pointer is used nevertheless 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.
[edit] Thin Pointers
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.
[edit] Fat Pointers
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.
For pointers of this kind, System.Address_to_Access_Conversion will probably not work satisfactorily.
[edit] Example
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(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.
[edit] Declaring access types
[edit] 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). It 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 with an access object of a type different from the one it was allocated with is erroneous.)
type Day_Of_Month is range 1 .. 31; type Day_Of_Month_Access is access Day_Of_Month;
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 Day_Of_Month'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 Day_Of_Month_Access'Storage_Pool use Pool_Name;
[edit] Access all
These access types are called general access types. They handle accesses to objects created on any storage pool, on the stack or at library level (static). Conversion between general access types are allowed, but subject to certain access level checks.
type Day_Of_Month is range 1 .. 31; type Day_Of_Month_Access is access all Day_Of_Month;
[edit] Access constant
General access types granting read-only access of the referenced object, whether it be intrinsically a constant or a variable.
type Day_Of_Month is range 1 .. 31; type Day_Of_Month_Access is access constant Day_Of_Month;
[edit] Anonymous access
An anonymous access is used as a parameter to a function, procedure or a discriminated type. Here some examples:
procedure Test (Some_Day : access Day_Of_Month);
task type Thread ( Execute_For_Day : access Day_Of_Month) is -- ...In Ada 2005 a end Thread;
type Day_Data ( Store_For_Day : access Day_Of_Month) is record -- components end record;
Before you use an anonymous access you should consider if the "out" or "in out" modifier is not more appropriate - see access performance to see why.
This language feature is only available in Ada 2005.
In Ada 2005 an anonymous access is also allowed in more circumstances:
type Object is record M : Integer; Next: access Object; end record; X: access Integer; function F return access constant Float;
[edit] Not null access
This language feature is only available in Ada 2005.
All access types can be modified with "not null":
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 type must then always point to an object, so initializations are compulsory.
The compiler also allows you to declare a type directly as "not null access" instead of as a subtype of other access type:
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 would be nearly unusable (for example, you would be unable to free the allocated memory). Not null access are intended for access subtypes, object declarations, and subprogram parameters.[1]
[edit] Constant anonymous access
This language feature is only available in Ada 2005.
An anonymous access to a constant object.
procedure Test (Some_Day : access constant Day_Of_Month);
[edit] Access to subprogram
An access to subprogram allows us 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;
[edit] Anonymous access to subprogram
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.
[edit] Using access types
[edit] Explicit dereferencing
Given a pointer variable, the way for explicitly dereferencing it —that is, getting access to the pointed object— is to append .all.
[edit] Creating object in a storage pool
Objects in a storage pool are created with the keyword new:
Father : Person_Access := new Person'(Father_First_Name, Father_Last_Name);
[edit] Deleting object from a storage pool
Although the Ada standard mentions the use of a garbage collector, which would automatically remove all unneeded objects that have been created on the heap (storage pool), 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 a certain access type, prevents automatic garbage collection of objects created with it.
Therefore in order to delete an object from the heap, you will need the generic unit Ada.Unchecked_Deallocation. But note that deallocating objects with a different access type than the one with which they were created is erroneous (since the corresponding storage pools may be 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;
Because of the problems with dangling pointer, this operation is called unchecked deallocation. It is the chore of the programmer to take care that this does not happen.
Since Ada does allow for user defined storage pools you could also try a garbage collector library.
[edit] Implicit dereferencing
If you declare a record type, and an access type for it, thus:
type Person is record First_Name : String (1..30); Last_Name : String (1..20); end record; type Person_Access is access Person;
you can use these types as follows:
Father : Person_Access := new Person'(Father_First_Name, Father_Last_Name); ... Ada.Text_IO.Put(Father.all.First_Name);
However, .all is implicit if omitted, so we can write more concisely:
Ada.Text_IO.Put(Father.First_Name);
and be careful:
Obj1 : Person_Access := new Person'(...); Obj2 : Person_Access := new Person'(...); Obj1.all := Obj2.all; -- Obj1 still refers to an object different from Obj2, -- but it has the same content (deep copy). Obj1 := Obj2; -- Obj1 now refers to the same person as Obj2 (shallow copy)
Implicit dereferencing also applies to arrays:
type Vector is array (0..3) of Complex; type Vector_Access is access Vector; VA : Vector_Access := new Vector; ... C : Complex := VA(3); -- a shorter equivalent for VA.all(3)
[edit] Access FAQ
A few "Frequently Asked Question" and "Frequently Encountered Problems" (mostly from former C users) regarding Ada's access types.
[edit] Access vs. access all
An access all can do anything a simple access can do. So one might ask: "Why use access at all?" - And indeed some programmers never use simple access.
But this is wrong because access all is more error prone! One example: When Ada.Unchecked_Deallocation is used on an access all it is not checked that the object actually belongs to the proper storage pool - or if the object belongs to any storage pool at all. The following example, invalidly and perhaps disastrously, 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.
Depending on the implementation an access might also render better performance than access all.
[edit] Access performance
Ada performs run-time checks on access types, so not all access types render the same performance. In other places of the Ada Programming we suggest that you should not care about runtime checks since the optimizer will remove them when they are not needed. However former C programmers often use anonymous access — which have worse performance — instead of using "out" or "in out" parameter modes which have best performance. But note that parameter modes "in", "in out", "out" have nothing to do with parameter passing mechanisms "by copy", "by reference" etc; they only describe the data flow direction. The compiler is therefore free to choose whatever mechanism is the best for the data type and mode.
Here is a performance list — including the access modifiers — from best to worse:
- "in" modifier : parameter is passed into the subprogram.
- "out" modifier : parameter is passed from the subprogram.
- "in out" modifier : parameter may be modified by the subprogram.
- access all : access to any (aliased) object.
- pool access : the access is always within a storage pool and only checks for not null are performed upon dereference.
- anonymous access : complex "stack deeps level" checks are needed so an "anonymous access" can be safely converted from and to an "access all" or "pool access".
[edit] Access vs. System.Address
An access is not a memory address. It is 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, you may consider using the System.Address type.
[edit] C compatible pointer
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 should 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 a "normal" C programmer would have used them as well.
Of course the definition of a "normal" C programmer is not left to chance, the RM contains precise rules on when to use a pointer for "in", "out", and "in out" - see "B.3 Interfacing with C (Annotated)".
[edit] Where is void*?
While actually been a problem for "interfacing with C", here is 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);
[edit] See also
[edit] Wikibook
[edit] Ada Reference Manual
[edit] Ada 95
- 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)
[edit] Ada 2005
- 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)