Movatterモバイル変換


[0]ホーム

URL:


Intellectual Ammunition Department

Ada-95: A guide for C and C++ programmers

by Simon Johnston


Welcome


Summary

I have endeavered to present below a tutorial for C and C++ programmers to show themwhat Ada can provide and how to set about turning the knowledge and experiencethey have gained in C/C++ into good Ada programming. This really does expect the reader to be familiar with C/C++, although C only programmers should beable to read it OK if they skip section 3.

My thanks to S. Tucker Taft for the
mail that started me on this.

Contents

Introduction.
Document Status.
1 Ada Basics.
1.1 C/C++ types to Ada types.
1.1.1 Declaring new types and subtypes.
1.1.2 Simple types, Integers and Characters.
1.1.3 Strings.
1.1.4 Floating and Fixed point.
1.1.5 Enumerations and Ranges.
1.1.6 Arrays.
1.1.7 Records.
1.1.8 Access types (pointers).
1.1.9 Ada advanced types and tricks.
1.1.10 C Unions in Ada (food for thought).
1.2 C/C++ statements to Ada.
1.2.1 Compound Statement
1.2.2 if Statement
1.2.3 switch Statement
1.2.4 loops
1.2.4.1 loop
1.2.4.2 while loop
1.2.4.3 do loop
1.2.4.4 for loop
1.2.4.5 break and continue
1.2.5 return
1.2.6 labels and goto
1.2.7 exception handling
1.2.8 sub-programs
1.3 Ada Safety.
1.3.1 Static provability
1.3.2 Predefined exceptions and pragmas
1.3.3 Unchecked programming
2 Ada Packages.
2.1 What a package looks like
2.2 Include a package in another
2.3 Package data hiding
2.4 Hierarchical packages.
2.5 Renaming identifiers.
3 Ada-95 Object Oriented Programming.
3.1 The tagged type.
3.2 Constructors/Destructors for Ada.
3.3 Class member attributes.
3.4 Class member functions.
3.5 public/protected/private.
4 Generics
4.1 A generic procedure
4.2 Generic packages
4.3 Generic types and other parameters
5 IO
5.1 Ada.Text_IO
5.2 Ada.Sequential_IO and Ada.Direct_IO
5.3 Streams
6 Interfacing to other languages
6.1 C and C++
6.2 COBOL
6.3 Fortan
7 Concurrency
7.1 Tasks
7.1.1 Tasks as threads
7.1.2 A simple task
7.1.3 Tasks as types
7.2 Task synchronization (Rendezvous)
7.2.1 entries
7.2.2 select
7.2.3 gaurded entries
7.2.4 delays
7.2.5 select else
7.2.6 termination
7.2.7 conditional entry calls
7.3 Protected types
References

Introduction.

This document is written primarily for C and C++ programmers and is set out to describe the Ada programming language in a way more accessible to them. I have used the standard Ada documentation conventions, code will look likethis and keywords will look likethis. I will include references to the Ada Reference Manual in braces and in italics,{1.1}, which denotes section 1.1. The ARM is reference 1 at the end of this document. Another useful reference is the Lovelace on-line tutorialwhich is a great way to pick up Ada basics.

I will start out by describing the Ada predefined types, and the complextypes, and move onto the simple language constructs. Section 2 will startto introduce some very Ada specific topics and section 3 describes the new Ada-95 Object Oriented programming constructs. Section 5 describes theAda tools for managing concurrency, the task and protected types, these are worth investing some time getting to grips with. Section 6 is atour of the Ada IO library and covers some of the differences in concept and implementation between it and C stdio.

Please feel free to comment on errors, things you don't like and things you would like to see. If I don't get the comments then I can't take it forward,and the question you would like answered is almost certainly causing otherpeople problems too.

If you are new to Ada and do not have an Ada compiler handy then why not trythe GNAT Ada compiler. This compiler is based on the well known GCC C/C++ andObjective-C compiler and provides a high quality Ada-83 and Ada-95 compiler for many platforms. Here is theFTP site see if there is one for you.

Document Status.

This document is still under revision and I receive a number of mails askingfor improvements and fixing bugs and spelling mistakes I have introduced. I will try and keep this section up to date on what needs to be done and what Iwould like to do.

Current Status

Section 2
More on 2.3 (data hiding) and 2.4 (Hierarchical packages)
Section 3
First issue of this section, 3.6, 3.7, 3.8 and 3.9 have additional workplanned. They may also require re-work pending comments.
Section 5
Section 5.3 (streams) not yet done.
Section 6
New sections to be added for each language.
Section 7
Major re-work following comments from Bill Wagner, 7.2.7 added, requiressome more words, and section 7.3 requires more justification etc.

Wish List

I would like to use a consistant example throughout, building it up as wego along. The trouble is I don't think I have space in an HTML page to do this.


1 Ada Basics.

This section hopes to give you a brief introduction to Ada basics, such astypes, statements and packages. Once you have these you should be able toread quite a lot of Ada source without difficulty. You are expected toknow these things as we move on so it is worth reading.

One thing before we continue, most of the operators are similar, but you should notice these differences:

OperatorC/C++Ada
Assignment=:=
Equality===
NonEquality!=/=
PlusEquals+=
SubtractEquals-=
MultiplyEquals*=
DivisionEquals/=
OrEquals|=
AndEquals&=
Modulus%mod
Remainderrem
AbsoluteValueabs
Exponentiation**
Range..

One of the biggest things to stop C/C++ programmers in their tracks is that Ada is case insensitive, sobegin BEGIN Begin are all the same.This can be a problem when porting case sensitive C code into Ada.

Another thing to watch for in Ada source is the use of ' the tick. The tickis used to access attributes for an object, for instance the following codeis used to assign to value s the size in bits of an integer.

int a = sizeof(int) * 8;a : Integer := Integer'Size;
Another use for it is to access the attributesFirst andLast, so for an integer the range of possible values isInteger'First to Integer'Last. This can also be applied to arrays so if you are passed an array and don't know the size of it you can use these attribute values to range over it in a loop (see section1.1.5). The tick is also used for other Ada constructs as well as attributes, for example character literals, code statements and qualified expressions (1.1.8).

1.1 C/C++ types to Ada types.

This section attempts to outline how to move C/C++ type declarations into anAda program and help you understand Ada code. Section 1.1.8 introduces someAda specific advanced topics and tricks you can use in such areas as bit fields, type representation and type size.

Note that 'objects' are defined in reverse order to C/C++, the object name isfirst, then the object type, as in C/C++ you can declare lists of objects byseperating them with commas.

int i;int a, b, c;int j = 0;int k, l = 1;i : Integer;a, b, c : Integer;j : Integer := 0;k, l : Integer := 1;
The first three declarations are the same, they create the same objects, and the third one assigns j the value 0 in both cases. However the fourth examplein C leaves k undefined and creates l with the value 1. In the Ada example itshould be clear that both k and l are assigned the value 1.

Another difference is in defining constants.

const int days_per_week = 7;days_per_week :constant Integer := 7;days_per_week :constant := 7;
In the Ada example it is possible to define a constant without type, the compiler then chooses the most appropriate type to represent it.

1.1.1 Declaring new types and subtypes.

Before we delve into descriptions of the predefined Ada types it is importantto show you how Ada defines a type.

Ada is a strongly typed language, in fact possibly the strongest. This meansthat its type model is strict and absolutely stated. In C the use of typedefintroduces a new name which can be used as a new type, though the weak typingof C and even C++ (in comparison) means that we have only really introduceda very poor synonym. Consider:

typedef int INT;INT a;int b;a = b; // works, no problem
The compiler knows that they are both ints. Now consider:
type INTis new Integer;a : INT;b : Integer;a := b; -- fails.
The important keyword isnew, which really sums up the way Ada is treating that line, it can be read as "a new typeINThas been created from the typeInteger", whereas the C line may be interpreted as "a new nameINT has been introduced as a synonym forint".

This strong typing can be a problem, and so Ada also provides you with afeature for reducing the distance between the new type and its parent, consider

subtype INTis Integer;a : INT;b : Integer;a := b; -- works.
The most important feature of the subtype is to constrain the parent type insome way, for example to place an upper or lower boundary for an integervalue (see section below on ranges).

1.1.2 Simple types, Integers and Characters.

We have seen above the Integer type, there are a few more with Ada, theseare listed below.
Integer, Long_Integer etc.
Any Ada compiler must provide the Integer type, this is a signed integer,and of implementation defined size. The compiler is also at liberty to provideLong_Integer, Short_Integer, Long_Long_Integer etc as needed.

Unsigned Integers
Ada does not have a defined unsigned integer, so this can be synthesised by a range type (see section 1.1.5), and Ada-95 has a defined package,System.Unsigned_Types which provide such a set of types.

Ada-95 has added amodular type which specifies the maximum value, andalso the feature that arithmatic is cyclic, underflow/overflow cannot occur. Thismeans that if you have a modular type capable of holding values from 0 to 255, and its current value is 255, then incrementing it wraps it around to zero. Contrast this with range types (previously used to define unsigned integer types)in section1.1.5 below.Such a type is defined in the form:

type BYTEis mod 256;type BYTEis mod 2**8;
The first simply specifies the maximum value, the second specifies it in a more'precise' way, and the 2**x form is often used in system programming to specifybit mask types.Note: it is not required to use 2**x, you can use anyvalue, so 10**10 is legal also.

Character{3.5.2}
This is very similar to the C char type, and holds the ASCII character set.However it is actually defined in the packageStandard{A.1}as an enumerated type (see section 1.1.5). There is an Ada equivalent of the C set of functions inctype.h which is the packageAda.Characters.Handling.

Ada Also defines aWide_Character type for handling non ASCII character sets.

Boolean{3.5.3}
This is also defined in the packageStandard as an enumerated type (see below) as(FALSE, TRUE).

1.1.3 Strings.{3.6.3}

Heres a god send to C/C++ programmers, Ada has a predefined String type (definedagain inStandard). There is a good set of Ada packages for string handling, much better defined than the set provided by C, and Ada has a & operator for string concatenation.

As in C the basis for the string is an array of characters, so you can usearray slicing (see below) to extract substrings, and define strings of setlength. What, unfortunatly, you cannot do is use strings as unbounded objects,hence the following.

type A_Recordis  record    illegal : String;    legal   : String(1 .. 20);end record;procedure check(legal :in String);
The illegal structure element is because Ada cannot use 'unconstrained' typesin static declarations, so the string must be constrained by a size. Also notethat the lower bound of the size must be greater than or equal to 1, the C/C++array[4] which defines a range0..3 cannot be usedin Ada,1..4 must be used. One way to specify the size is by initialisation, for example:
Name : String := "Simon";
is the same as definingName as aString(1..5) andassigning it the value"Simon" seperatly..

For parameter types unconstrained types are allowed, similar to passingint array[] in C.

To overcome the constraint problem for strings Ada has a predefined packageAda.Strings.Unbounded which implements a variable length string type.

1.1.4 Floating{3.5.7} and Fixed{3.5.9} point.

Ada has two non-integer numeric types, the floating point and fixed point types. The predefined floating point type isFloat and compilers may addLong_Float, etc. A new Float type may be defined in one of two ways:
type FloatingPoint1is new Float;type FloatingPoint2is digits 5;
The first simply makes a new floating point type, from the standardFloat, with the precision and size of that type, regardless of what it is.

The second line asks the compiler to create a new type, which is a floating point type "of some kind" with a minimum of 5 digits of precision. This is invaluable when doing numeric intensive operations and intend to port the program, you define exactly the type you need, not what you think might do today.

If we go back to the subject of the tick, you can get the number of digits which are actually used by the type by the attribute 'Digits. So having saidwe want a type with minimum of 5 digits we can verify this:

number_of_digits : Integer := FloatingPoint2'Digits;

Fixed point types are unusual, there is no predefined type 'Fixed' and suchtype must be declared in the long form:

type Fixedis delta 0.1range -1.0 .. 1.0;
This defines a type which ranges from -1.0 to 1.0 with an accuracy of 0.1. Eachelement, accuracy, low-bound and high-bound must be defined as a real number.

There is a specific form of fixed point types (added by Ada-95) called decimal types. These add a clausedigits, and therangeclause becomes optional.

type Decimalis delta 0.01digits 10;
This specifies a fixed point type of 10 digits with two decimal places. Thenumber of digits includes the decimal part and so the maximum range of valuesbecomes-99,999,999.99 ...+99,999,999.99

1.1.5 Enumerations{3.5.1} and Ranges.

Firstly enumerations. These are not at all like C/C++s enums, they are truesets and the fact that the Boolean type is in fact:
type Booleanis (FALSE, TRUE);
should give you a feeling for the power of the type.

You have already seen a range in use (for strings), it is expressed aslow .. high and can be one of the most useful ways of expressing interfaces and parameter values, for example:

type Hoursis new Integerrange 1 .. 12;type Hours24is range 0 .. 23;type Minutesis range 1 .. 60;
There is now no way that a user can pass us an hour outside the range we havespecified, even to the extent that if we define a parameter of typeHours24 we cannot assign a value ofHours even though it can only be in the range. Another feature is demonstrated, forHours we have said we want to restrict anInteger type to the given range, for the next two we have asked the compiler tochoose a type it feels appropriate to hold the given range, this is a nice way to save a little finger tapping, but should be avoided Ada provides youa perfect environment to specify precisely what you want, use it the firstdefinition leavesnothing to the imagination.

Now we come to the rules on subtypes for ranges, and we will define the twoHours again as follows:

type Hours24is new range 0 .. 23;subtype Hoursis Hours24range 1 .. 12;
This limits the range even further, and as you might expect a subtype cannotextend the range beyond its parent, sorange 0 .. 25 would havebeen illegal.

Now we come to the combining of enumerations and ranges, so that we mighthave:

type All_Daysis (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);subtype Week_Daysis All_Daysrange Monday .. Friday;subtype Weekendis All_Daysrange Saturday .. Sunday;
We can now take aDay, and see if we want to go to work:
Day : All_Days := Today;if Dayin Week_Daysthen  go_to_work;end if;
Or you could use the formif Dayin range Monday .. Friday and we would not need the extra types.

Ada provides four useful attributes for enumeration type handling, note these are used slightly differently than many other attributes as they are applied to the type, not the object.

Succ
This attribute supplies the 'successor' to the current value, so the'Succ value of an object containingMonday isTuesday.
Note: If the value of the object isSunday then an exception is raised, you cannotSucc past the end of the enumeration.
Pred
This attribute provides the 'predecessor' of a given value, so the'Pred value of an object containingTuesday isMonday.
Note: the rule above still applies'Pred ofMonday is an error.
Val
This gives you the value (as a member of the enumeration) of element n inthe enumeration. ThusVal(2) isWednesday.
Note: the rule above still applies, and note also that'Val(0) is the same as'First.
Pos
This gives you the position in the enumeration of the given element name.Thus'Pos(Wednesday) is2.
Note: the range rules still apply, also that'Last will work, and returnSunday.
All_Days'Succ(Monday) = TuesdayAll_Days'Pred(Tuesday) = MondayAll_Days'Val(0) = MondayAll_Days'First = MondayAll_Days'Val(2) = WednesdayAll_Days'Last = SundayAll_Days'Succ(All_Days'Pred(Tuesday)) = Tuesday
Ada also provides a set of 4 attributes for range types, these are intimatlyassociated with those above and are:
First
This provides the value of the first item in a range. Considering the range
0 .. 100 then'First0.
Last
This provides the value of the last item in a range, and so considering above,'Last is100.
Length
This provides the number of items in a range, so'Length isactually101.
Range
This funnily enough returns in this case the value we gave it, but you willsee when we come onto arrays how useful this feature is.
As you can see these have no direct C/C++ equivalent and are part of the reasonfor Ada's reputation for safety, you can define for a parameter exactly therange of values it might take, it all amounts to better practice for large developments where your interface is read by many people who may not be ableto tell that the integer parameter day starts at 0, which indicates Wednesdayetc.

1.1.6 Arrays{3.6}.

Arrays in Ada make use of the range syntax to define their bounds and can bearrays of any type, and can even be declared as unknown size.

Some example:

char name[31];int  track[3];int  dbla[3][10];int  init[3] = { 0, 1, 2 };typedef char[31] name_type;track[2] = 1;dbla[0][3] = 2;Name  :array (0 .. 30)of Character; -- ORName  : String (1 .. 30);Track :array (0 .. 2)of Integer;DblA  :array (0 .. 2)of array (0 .. 9)of Integer; -- ORDblA  :array (0 .. 2,0 .. 9)of Integer;Init  :array (0 .. 2)of Integer := (0, 1, 2);type Name_Typeis array (0 .. 30)of Character;track(2) := 1;dbla(0,3) := 2;-- Note try this in C.a, b : Name_Type;a := b; -- will copy all elements of b into a.
Simple isn't it, you can convert C arrays into Ada arrays very easily. Whatyou don't get is all the things you can do with Ada arrays that you can't doin C/C++.

non-zero based ranges.
Because Ada uses ranges to specify the bounds of an array then you caneasily set the lower bound to anything you want, for example:
Example :array (-10 .. 10)of Integer;
non-integer ranges.
In the examples above we have used the common abbreviation for range specifiers. The ranges above are all integer ranges, and so we did not need touse the correct form which is:
array(typerange low .. high)
which would make Example abovearray(Integerrange -10 .. 10). Now you can see where we're going, take an enumerated type,All_Days and you can define an array:
Hours_Worked :array (All_Daysrange Monday .. Friday);
unbounded array types.
The examples above did demonstrate how to declare an array type. One ofAda's goals is reuse, and to have to define a function to deal with a 1..10array, and another for a 0..1000 array is silly. Therefore Ada allows you to define unbounded array types. An unbounded type can be used as a parametertype, but you cannot simply define a variable of such a type. Consider:
type Vectoris array (Integerrange<>)of Float;procedure sort_vector(sort_this :in out Vector);Illegal_Variable : Vector;Legal_Variable   : Vector(1..5);subtype SmallVectoris Vector(0..1);Another_Legal    : SmallVector;
This does allow us great flexibility to define functions and procedures to work on arrays regardless of their size, so a call tosort_vectorcould take theLegal_Variable object or an object of typeSmallVector, etc.Note that a variable of typeSmallvector is constrained and so can be legally created.

array range attributes.
If you are passed a type which is an unbounded array then if you want to loopthrough it then you need to know where it starts. So we can use the range attributesintroduced in1.1.5 to iterate over a given array thus:attributes for array types. Consider:
Example :array (1 .. 10)of Integer;for iin Example'First .. Example'Lastloopfor iin Example'Rangeloop
Note that if you have a multiple dimension array then the above notationimplies that the returned values are for the first dimension, use the notationArray_Name(dimension)'attribute for multi-dimensional arrays.

Initialisation by range (Aggregates{}???)
When initialising an array one can initialise a range of elements in onego:
Init :array (0 .. 3)of Integer := (0 .. 3 => 1);Init :array (0 .. 3)of Integer := (0 => 1,others => 0);
The keywordothers sets any elements not explicitly handled.

Slicing
Array slicing is something usually done with memcpy in C/C++. Take asection out of one array and assign it into another.
Large :array (0 .. 100)of Integer;Small :array (0 .. 3)of Integer;-- extract section from one array into another.Small(0 .. 3) := Large(10 .. 13);-- swap top and bottom halfs of an array.Large := Large(51 .. 100) & Large(1..50);
Note: Both sides of the assignment must be of the same type, that isthe same dimensions with each element the same. The following is illegal.
-- extract section from one array into another.Small(0 .. 3) := Large(10 .. 33);--                     ^^^^^^^^ range too big.

1.1.7 Records{3.8}.

You shouldn't have too much problem here, you can see an almost direct mappingfrom C/C++ to Ada for simple structures. Note the example below does not try to convert type to type, thus the C char*, to hold a string is converted to theAda String type.
struct _device {  int   major_number;  int   minor_number;  char  name[20];};typedef struct _device Device;type struct_deviceisrecord    major_number : Integer;    minor_number : Integer;    name         : String(1 .. 19);end record;type Deviceis new struct_device;
As you can see, the main difference is that the name we declare for the initialrecord is a type, and can be used from that point on. In C all we have declaredis a structure name, we then require the additional step of typedef-ing to adda new type name.

Ada uses the same element reference syntax as C, so to access the minor_number element of an object lp1 of type Device we writelp1.minor_number.Ada does allow, like C, the initialisation of record members at declaration.In the code below we introduce a feature of Ada, the ability to name the elements we are going to initialise. This is useful for clarity of code, but more importantly it allows us to only initialise the bits we want.

Device lp1 = {1, 2, "lp1"};lp1 : Device := (1, 2, "lp1");lp2 : Device := (major_number => 1,                 minor_number => 3,                 name         => "lp2");tmp : Device := (major_number => 255,                 name         => "tmp");
When initialising a record we use anaggregate, a construct which groupstogether the members. This facility (unlike aggregates in C) can also be used to assign members at other times as well.
tmp : Device;-- some processingtmp := (major_number => 255, name => "tmp");
This syntax can be used anywhere where parameters are passed, initialisation (as above) function/procedure calls, variants and discriminants and generics.The code above is most useful if we have a default value for minor_number, sothe fact that we left it out won't matter. This is possible in Ada.

This facility improves readability and as far as most Ada programmers believemaintainability.

type struct_deviceisrecord    major_number : Integer          := 0;    minor_number : Integer          := 0;    name         : String(1 .. 19)  := "unknown";end record;
Structures/records like this are simple, and there isn't much more to say. Themore interesting problem for Ada is modelling C unions (see section1.1.10).

1.1.8 Access types (pointers){3.10}.

The topic of pointers/references/access types is the most difficult, each language has its own set of rules and tricks. In C/C++ the thing you mustalways remember is that the value of a pointer is the real memory address, inAda it is not. It is a type used to access the data.

Ada access types are safer, and in some ways easier to use and understand, butthey do mean that a lot of C code which uses pointers heavily will have to bereworked to use some other means.

The most common use of access types is in dynamic programming, for example inlinked lists.

struct _device_event {  int  major_number;  int  minor_number;  int  event_ident;  struct _device_event* next;};type Device_Event;type Device_Event_Accessis access Device_Event;type Device_Eventisrecord    major_number : Integer := 0;    minor_number : Integer := 0;    event_ident  : Integer := 0;    next         : Device_Event_Access :=null;    -- Note: the assignement to null is not required,    -- Ada automatically initialises access types to    -- null if no other value is specified.end record;
The Ada code may look long-winded but it is also more expressive, the accesstype is declared before the record so a real type can be used for the declaration of the element next.Note: we have to forward declare therecord before we can declare the access type, is this extra line worth allthe moans we hear from the C/C++ community that Ada is overly verbose?

When it comes to dynamically allocating a new structure the Ada allocator syntax is much closer to C++ than to C.

Event_1 :=new Device_Event;Event_1.next :=new Device_Event'(1, 2, EV_Paper_Low,null);
There are three things of note in the example above. Firstly the syntax, wecan say directly that we want a newthing, none of this malloc rubbish.Secondly that there is no difference in syntax between access of elements ofa statically allocated record and a dynamically allocated one. We use therecord.element syntax for both. Lastly that we can initialise the valuesas we create the object, the tick is used again, not as an attribute, but withparenthases in order to form a qualified expresssion.

Ada allows you to assign between access types, and as you would expect it onlychanges what the access type points to, not the contents of what it points to.One thing to note again, Ada allows you to assign one structure to another ifthey are of the same type, and so a syntax is required to assign the contentsof an access type, its easier to read than write, so:

dev1, dev2 : Device_Event;pdv1, pdv2 : Device_Event_Access;dev1 := dev2; -- all elements copied.pdv1 := pdv2; -- pdv1 now points to contents of pdv2.pdv1.all := pdv2.all; -- !!
What you may have noticed is that we have not discussed the operator to freethe memory we have allocated, the equivalent of C's free() or C++'s delete.

There is a good reason for this,Ada does not have one.

To digress for a while, Ada was designed as a language to support garbagecollection, that is the runtime would manage deallocation of no longer requireddynamic memory. However at that time garbage collection was slow, required alarge overhead in tracking dynamic memory and tended to make programs irraticin performance, slowing as the garbage collector kicks in. The language specification therefore states{13.11} "An implementation need not supportgarbage collection ...". This means that you must, as in C++ manage yourown memory deallocation.

Ada requires you to use the generic procedureUnchecked_Deallocation(see1.3.4) to deallocate a dynamic object. This procedure must be instantiated for each dynamic type and should not (ideally) be declared on a public package spec, ie provide the client with a deallocation procedure which usesUnchecked_Deallocation internally.

1.1.9 Ada advanced types and tricks.

Casting (wow)
As you might expect from what we have seen so far Ada must allow us someway to relax the strong typing it enforces. In C the cast allows us to makeanything look like something else, in Adatype coersion can allow youto convert between two similar types, ie:
type Thingis new Integer;an_Integer : Integer;a_Thing : Thing;an_Integer := a_Thing; -- illegalan_Integer := Integer(a_Thing);
This can only be done between similar types, the compiler will not allow suchcoersion between very different types, for this you need the generic procedureUnchecked_Conversion (see1.3.4) which takes as an argument one type, and returns another. The only constraint on this is that they must be the same size.

Procedure types.{}
Ada-83 did not allow the passing of procedures as subprogram parameters at execution time, or storing procedures in records etc. The rationale for this was that it broke the ability to statically prove the code. Ada-95 has introduced the ability to define types which are in effect similar to C's ability to define pointers to functions.

In C/C++ there is the most formidable syntax for defining pointers to functionsand so the Ada syntax should come as a nice surprise:

typedef int (*callback_func)(int param1, int param2);type Callback_Funcis access function(param_1 :in Integer;                                      param_2 :in Integer)return Integer;
Discriminant types{3.7}.
Discriminant types are a way of parameterising a compound type (such as arecord, tagged, task or protected type). For example:
type Event_Itemisrecord    Event_ID   : Integer;     Event_Info : String(1 .. 80);end record;type Event_Log(Max_Size : Integer)isrecord    Log_Opened : Date_Type;    Events     : array (1 .. Max_Size)of Event_Item;end record;
First we declare a type to hold our event information in. We then declare atype which is a log of such events, this log has a maximum size, and ratherthan the C answer, define an array large enough for the maximum ever, or resortto dynamic programming the Ada approach is to instantiate the record with amax value and at time of instantiation define the size of the array.
My_Event_Log : Event_Log(1000);
If it is known that nearly all event logs are going to be a thousand items insize, then you could make that a default value, so that the following code isidentical to that above.
type Event_Log(Max_Size : Integer := 1000)isrecord    Log_Opened : Date_Type    Events     : array (Integer range 1 .. Max_Size)of Event_Item;end record;My_Event_Log : Event_Log;
Again this is another way in which Ada helps, when defining an interface, to state precisely what we want to provide.

Variant records{3.8.1}.
Anyone who has worked in a Pascal language will recognise variant records,they are a bit like C/C++ unions except that the arevery different :-)

Ada variant records allow you to define a record which has 2 or more blocks ofdata of which only one is visible at any time. The visibility of the block isdetermined by a discriminant which is then 'cased'.

type Transport_Typeis (Sports, Family, Van);type Car(Type : Transport_Type)isrecord    Registration_Date : Date_Type;    Colour            : Colour_Type;case Typeiswhen Sports =>        Soft_Top      : Boolean;when Family =>        Number_Seats  : Integer;        Rear_Belts    : Boolean;when Van    =>        Cargo_Capacity: Integer;end case;end record;
So if you codeMy_Car : Car(Family); then you can ask for thenumber of seats in the car, and whether the car has seat belts in the rear, but you cannot ask if it is a soft top, or what its cargo capacity is.

I guess you've seen the difference between this and C unions. In a C unionrepresentation of the above any block is visible regardless of what typeof car it is, you can easily ask for the cargo capacity of a sports car and Cwill use the bit pattern of the boolean to provide you with the cargo capacity.Not good.

To simplify things you can subtype the variant record with types which definethe variant (note in the example the use of the designator for clarity).

subtype Sports_Caris Car(Sports);subtype Family_Caris Car(Type => Family);subtype Small_Vanis Car(Type => Van);
Exceptions{11.1}.
Exceptions are a feature which C++ is only now getting to grips with, although Ada was designed with exceptions included from the beginning. Thisdoes mean that Ada code will use exceptions more often than not, and certainlythe standard library packages will raise a number of possible exceptions.

Unlike C++ where an exception is identified by its type in Ada they are uniquely identified by name. To define an exception for use, simply

parameter_out_of_range : Exception;
These look and feel like constants, you cannot assign to them etc, you can onlyraise an exception and handle an exception.

Exceptions can be argued to be a vital part of the safety of Ada code, theycannot easily be ignored, and can halt a system quickly if something goeswrong, far faster than a returned error code which in most cases is completelyignored.

System Representation of types{13}.
As you might expect with Ada's background in embedded and systems programming there are ways in which you can force a type into specific systemrepresentations.
type BYTEis range 0 .. 255;for BYTEuse 8;
This first example shows the most common form of system representation clause,the size attribute. We have asked the compiler to give us a range, from 0 to255 and the compiler is at liberty to provide the best type available to holdthe representation. We are forcing this type to be 8 bits in size.
type DEV_Activityis (READING, WRITING, IDLE);for DEV_Activityuse (READING => 1, WRITING => 2, IDLE => 3);
Again this is useful for system programming it gives us the safety of enumerationrange checking, so we can only put the correct value into a variable, but doesallow us to define what the values are if they are being used in a call whichexpects specific values.
type DEV_Availableis BYTE;for DEV_Availableuse at 16#00000340#;
This example means that all objects of typeDEV_Available are placed at memory address 340 (Hex). This placing of data items can be done ona per object basis by using:
type DEV_Availableis BYTE;Avail_Flag : DEV_Available;for Avail_Flag'Addressuse 16#00000340#;
Note the address used Ada's version of the C 0x340 notation, however the general form isbase#number# where the base can be anything, including 2, so bit masks are real easy to define, for example:
Is_Available :constant BYTE := 2#1000_0000#;Not_Available:constant BYTE := 2#0000_0000#;
Another feature of Ada is that any underscores in numeric constants are ignored,so you can break apart large numbers for readability.
type DEV_Statusis 0 .. 15;type DeviceDetailsisrecord     status : DEV_Activity;    rd_stat: DEV_Status;    wr_stat: DEV_Status;end record;for DeviceDetailsuserecord at mod 2;    statusat 0range 0 .. 7;    rd_statat 1range 0 .. 3;    wr_statat 1range 4 .. 7;end record;
This last example is the most complex, it defines a simple range type, anda structure. It then defines two things to the compiler, first the mod clause setsthe byte packing for the structure, in this case back on two-byte boundaries.The second part of this structure defines exactly the memory image of therecord and where each element occurs. The number after the 'at' is the byte offsetand the range, or size, is specified in number of bits.

>From this you can see that the whole structure is stored in two bytes wherethe first byte is stored as expected, but the second and third elements ofthe record share the second byte, low nibble and high nibble.

This form becomes very important a little later on.

1.1.10 C Unions in Ada, (food for thought).

Ada has more than one way in which it can represent a union as defined in a Cprogram, the method you choose depends on the meaning and usage of the C union.

Firstly we must look at the two ways unions are identified. Unions are used to represent the data in memory in more than one way, the programmermust know which way is relevant at any point in time. This variant identifiercan be inside the union or outside, for example:

struct _device_input {  int device_id;  union {    type_1_data from_type_1;    type_2_data from_type_2;  } device_data;};void get_data_func(_device_input* from_device);union device_data {  type_1_data from_type_1;  type_2_data from_type_2;};void get_data_func(int *device_id, device_data* from_device);
In the first example all the data required is in the structure, we call thefunction and get back a structure which holds the union and the identifier which denotes which element of the union is active. In the second exampleonly the union is returned and the identifier is seperate.

The next step is to decide whether, when converting such code to Ada, you wishto maintain simply the concept of the union, or whether you are required tomaintain the memory layout also.Note: the second choice is usually onlyif your Ada code is to pass such a structure to a C program or get one from it.

If you are simply retaining the concept of the union then you wouldnotuse the second form, use the first form and use a variant record.

type Device_IDis new Integer;type Device_Input(From_Device : Device_ID)isrecordcase From_Deviceiswhen 1 =>        From_Type_1 : Type_1_Data;when 2 =>        From_Type_2 : Type_2_Data;end case;end record;
The above code is conceptually the same as the first piece of C code, howeverit will probably look very different, you could use the following representationclause to make it look like the C code (type sizes are not important).
for Device_Inputuserecord    From_Deviceat 0range 0 .. 15;    From_Type_1at 2range 0 .. 15;    From_Type_2at 2range 0 .. 31;end record;
You should be able to pass this to and from C code now. You could use arepresentation clause for the second C case above, but unless you really mustpass it to some C code then re-code it as a variant record.

We can also use the abilities ofUnchecked_Conversion to convert between different types (see1.3.4). This allows us towrite the following:

type Type_1_Dataisrecord    Data_1 : Integer;end record;type Type_2_Dataisrecord    Data_1 : Integer;end record;function Type_1_to_2is new Unchecked_Conversion  (Source => Type_1_data, Target => Type_2_Data);
This means that we can read/write items of typeType_1_Data and when we need to represent the data asType_2_Data we can simplywrite
  Type_1_Object : Type_1_Data := ReadData;  :  Type_2_Object : Type_2_Data := Type_1_to_2(Type_1_Object);

1.2 C/C++ statements to Ada.

I present below the set of C/C++ statement types available, with each itsAda equivalent.

Note: All Ada statements can be qualified by a name, this be discussedfurther in the section on Ada looping constructs, however it can be used anywhere to improve readability, for example:

begin  Init_Code:begin      Some_Code;end Init_Code;  Main_Loop:loopif Some_Valuethenexit loop Main_Loop;end if;end loop Main_Loop;  Term_Code:begin      Some_Code;end Term_Code;end A_Block;

1.2.1 Compound Statement{5.6}

A compound statement is also known as a block and in C allows you to definevariables local to that block, in C++ variables can be defined anywhere. InAda they must be declared as part of the block, but must appear in the declarepart just before the block starts.
{  declarations  statements}declare  declarationsbegin  statementend;

1.2.2 if Statement{5.3}

If statements are the primary selection tool available to programmers. TheAda if statement also has the 'elsif' construct (which can be used more than once in any if statement), very useful for large complex selections where a switch/case statement is not possible.

Note: Ada does not require brackets around the expressions used in if,case or loop statements.

if (expression) {  statement} else {  statement}if expressionthen   statementelsif expressionthen  statementelse  statementend if;

1.2.3 switch Statement{5.4}

The switch or case statement is a very useful tool where the number of possiblevalues is large, and the selection expression is of a constant scalar type.
switch (expression) {  case value: statement  default:    statement}case expressioniswhen value => statementwhen others => statementend case;
There is a point worth noting here. In C the end of the statement block between case statements is a break statement, otherwise we drop through into the next case. In Ada this does not happen, the end of the statement isthe next case.

This leads to a slight problem, it is not uncommon to find a switch statementin C which looks like this:

switch (integer_value) {case 1:case 2:case 3:case 4:  value_ok = 1;  break;case 5:case 6:case 7:  break;}
This uses ranges (see1.1.5) to select a set of values for a single operation, Ada also allows you to or values together, consider the following:
case integer_valueiswhen 1 .. 4    => value_ok := 1;when 5 | 6 | 7 =>null;end case;
You will also note that in Ada there must be a statement for each case, so we have to use the Adanull statement as the target of the second selection.

1.2.4 Ada loops{5.5}

All Ada loops are built around the simpleloop ... endconstruct
loop  statementend loop;

1.2.4.1 while Loop

The while loop is common in code and has a very direct Ada equivalent.
while (expression){  statement}while expressionloop  statementend loop;

1.2.4.2 do Loop

The do loop has no direct Ada equivalent, though section 1.2.4.5 will showyou how to synthesize one.
do{  statement}while (expression)-- no direct Ada equivalent.

1.2.4.3 for Loop

The for loop is another favourite, Ada has no direct equivalent to the C/C++ for loop (the most frighteningly overloaded statement in almost anylanguage) but does allow you to iterate over a range, allowing you access tothe most common usage of the for loop, iterating over an array.
for (init-statement ; expression-1 ; loop-statement){  statement}for identin rangeloop  statementend loop;
However Ada adds some nice touches to this simple statement.

Firstly, the variable ident is actually declared by its appearance in the loop, it is a new variable which exists for the scope of the loop only and takes the correct type according to the specified range.

Secondly you will have noticed that to loop for 1 to 10 you can write the following Ada code:

for iin 1 .. 10loopnull;end loop;
What if you want to loop from 10 down to 1? In Ada you cannot specify a rangeof10 .. 1 as this is defined as a 'null range'. Passing a nullrange to a for loop causes it to exit immediatly. The code to iterate over anull range such as this is:
for iin reverse 1 .. 10loopnull;end loop;

1.2.4.5 break and continue

In C and C++ we have two useful statements break and continue which may beused to add fine control to loops. Consider the following C code:
while (expression) {  if (expression1) {    continue;  }  if (expression2) {    break;  }}
This code shows how break and continue are used, you have a loop which takesan expression to determine general termination procedure. Now let us assumethat during execution of the loop you decide that you have completed what youwanted to do and may leave the loop early, the break forces a 'jump' to the next statement after the closing brace of the loop. A continue is similar butit takes you to the first statement after the opening brace of the loop, ineffect it allows you to reevaluate the loop.

In Ada there is no continue, and break is now exit.

while expressionloopif expression2thenexit;end if;end loop;
The Ada exit statement however can combine the expression used to decide that it is required, and so the code below is often found.
while expressionloopexit when expression2;end loop;
This leads us onto the do loop, which can now be coded as:
loop  statementexit when expression;end loop;
Another useful feature which C and C++ lack is the ability to 'break' out ofnested loops, consider
while ((!feof(file_handle) && (!percent_found)) {    for (char_index = 0; buffer[char_index] != '\n'; char_index++) {        if (buffer[char_index] == '%') {            percent_found = 1;            break;        }        // some other code, including get next line.    }}
This sort of code is quite common, an inner loop spots the termination conditionand has to signal this back to the outer loop. Now consider
Main_Loop:while not End_Of_File(File_Handle)loopfor Char_Indexin Buffer'Rangeloopexit when Buffer(Char_Index) = NEW_LINE;exit Main_Loopwhen Buffer(Char_Index) = PERCENT;end loop;end loop Main_Loop;

1.2.5 return{6.5}

Here again a direct Ada equivalent, you want to return a value, then returna value,
return value; // C++ returnreturn value; -- Ada return

1.2.6 labels and goto{5.8}

Don't do it !!, OK one day you might need to, so heres how. Declare a label and jump to it.
label:  goto label;<<label>>goto label;

1.2.7 exception handling{11.2}

Ada and the newer verions of C++ support exception handling for critical errors. Exception handling consists of three components, the exception, raisingthe exception and handling the exception.

In C++ there is no exception type, when you raise an exception you pass outany sort of type, and selection of the exception is done on its type. In Adaas seen above there is a 'psuedo-type' for exceptions and they are then selected by name.

Firstly lets see how you catch an exception, the code below shows the basicstructure used to protect statement1, and execute statement2 on detection ofthe specified exception.

try {  statement1}catch (declaration) {  statement2}begin  statement1exceptionwhen ident => statement2when others => statement2end;
Let us now consider an example, we will call a function which we know may raise a particular exception, but it may raise some we don't know about, sowe must pass anything else back up to whoever called us.
try {  function_call();} catch (const char* string_exception) {  if (!strcmp(string_exception, "the_one_we_want")) {    handle_it();  } else {    throw;  }} catch (...) {  throw;}begin  function_call;exceptionwhen the_one_we_want => handle_it;when others          =>raise;end;
This shows how much safer the Ada version is, we know exactly what we are waiting for and can immediately process it. In the C++ case all we know isthat an exception of type 'const char*' has been raised, we must then check itstill further before we can handle it.

You will also notice the similarity between the Ada exception catching codeand the Ada case statement, this also extends to the fact that the when statement can catch multiple exceptions. Ranges of exceptions are not possible,however you can or exceptions, to get:

begin  function_call;exceptionwhen the_one_we_want |       another_possibility => handle_it;when others              => raise;end;
This also shows the basic form for raising an exception, the throw statementin C++ and the raise statement in Ada. Both normally raise a given exception,but both when invoked with no exception reraise the last one. To raise theexception above consider:
throw (const char*)"the_one_we_want";raise the_one_we_want;

1.2.8 sub-programs

The following piece of code shows how C/C++ and Ada both declare and definea function. Declaration is the process of telling everyone that the functionexists and what its type and parameters are. The definitions are where youactually write out the function itself. (In Ada terms the function spec andfunction body).
return_type func_name(parameters);return_type func_name(parameters){  declarations  statement}function func_name(parameters)return return_type;function func_name(parameters)return return_typeis  declarationsbegin  statementend func_name
Let us now consider a special kind of function, one which does not return avalue. In C/C++ this is represented as a return type of void, in Ada this iscalled a procedure.
void func_name(parameters);procedure func_name(parameters);
Next we must consider how we pass arguments to functions.
void func1(int  by_value);void func2(int* by_address);void func3(int& by_reference); // C++ only.
These type of parameters are I hope well understood by C and C++ programmers,their direct Ada equivalents are:
type intis new Integer;type int_staris access int;procedure func1(by_value     :in     int);procedure func2(by_address   :in out int_star);procedure func3(by_reference :in out int);
Finally a procedure or function which takes no parameters can be written in two ways in C/C++, though only one is Ada.
void func_name();void func_name(void);int func_name(void);procedure func_name;function func_name return Integer;
Ada also provides two features which will be understood by C++ programmers, possibly not by C programmers, and a third I don't know how C does without:
Overloading
Ada allows more than one function/procedure with the same name as long asthey can be uniquely identified by their signature (a combination of their parameter and return types).

function Dayreturn All_Days;function Day(a_date :in Date_Type)return All_Days;
The first returns you the day of week, of today, the second the day of weekfrom a given date. They are both allowed, and both visible. The compiler decideswhich one to use by looking at the types given to it when you call it.

Operator overloading{6.6}
As in C++ you can redefine the standard operators in Ada, unlike C++ youcan do this outside a class, and for any operator, with any types. The syntaxfor this is to replace the name of the function (operators are always functions)with the operator name in quotes, ie:
function "+"(Left, Right :in Integer)return Integer;
Available operators are:

=<<=>>=
+-&absnot
*/modrem**
andorxor

Parameter passing modes
C++ allows three parameter passing modes, by value, by pointer and byreference (the default mode for Ada).
void func(int by_value, int* by_pointer, int& by_reference);
Ada provides two optional keywords to specify how parameters are passed,in andout. These are used like this:
procedure proc(Parameter : in     Integer);procedure proc(Parameter :    out Integer);procedure proc(Parameter : in out Integer);procedure proc(Parameter :        Integer);
If these keywords are used then the compiler can protect you even more, so ifyou have anout parameter it will warn you if you use itbefore it has been set, also it will warn you if you assign to anin parameter.

Note that you cannot mark parameters without in functionsas functions are used to return values, suchside affects are disallowed.

Default parameters{6.4.1}
Ada (and C++) allow you to declare default values for parameters, this means that when you call the function you can leave such a parameter off the callas the compiler knows what value to use.
procedure Create  (File :in out File_Type;   Mode :in     File_Mode := Inout_File;   Name :in     String    := "";   Form :in     String    := "");
This example is to be found in each of the Ada file based IO packages, it opens a file, given the file 'handle' the mode, name of the file and a systemindependant 'form' for the file. You can see that the simplest invokation ofCreate isCreate(File_Handle); which simply provides the handleand all other parameters are defaulted (In the Ada library a file name of ""implies opening a temporary file). Now suppose that we wish to provide the name of the file also, we would have to writeCreate(File_Handle, Inout_File,"text.file"); wouldn't we? The Ada answer is no. By using designators ashas been demonstrated above we could use the form:
Create(File => File_Handle,       Name => "text.file");
and we can leave the mode to pick up its default. This skipping of parametersis a uniquely Ada feature.

Nested procedures
Simple, you can define any number of procedures within the definition ofanother as long as they appear before the begin.
procedure Sort(Sort_This :in out An_Array)isprocedure Swap(Item_1, Item_2 :in out Array_Type)isbeginend Swap;beginend Sort;
Notes: you can get in a mess with both C++ and Ada when mixing overloadingand defaults. For example:
procedure increment(A_Value : A_Type);procedure increment  (A_Value :in out A_Type;    By      :in     Integer := 1);
If we call increment with one parameter which of the two above is called? Nowthe compiler will show such things up, but it does mean you have to thinkcarefully and make sure you use defaults carefully.

1.3 Ada Safety.

Ada is probably best known for its role in safetly critical systems. Ada is probably best known for its role in safety critical systems. Boeingstandardized on Ada as the language for the new 777, and I can assure yousuch a decision is not taken lightly.

Ada is also commonly assumed to be a military language, with the US Department of Defense its prime advocate, this is not the case, a number of commercial and government developments have now been implemented in Ada. Ada is an excellent choice if you wish to spend your development time solving yourcustomers problems, not hunting bugs in C/C++ which an Ada compiler would not have allowed.

1.3.1 Static provability.

Ada-83 did not provide Object Oriented features, and did not even provideprocedural types as such constructs meant that you could only follow thepath of the code at runtime. Ada-83 was statically provable, you could followthe route the code would take given certain inputs from the source code alone.This has been a great benefit and has provided Ada programmers with a great deal of confidence in the code they wrote.

Ada-95 has introduced these new features, Object Oriented programmingthrough tagged types and procedural types which make it more difficult to statically prove an Ada-95 program, but the language designers decided that such features merited their inclusion in the language to further another goal, that of high reuse.

1.3.2 Predefined exceptions and pragmas.

A number of exceptions can be raised by the standard library and/or theruntime environment. You may expect to come accross at least one while youare learning Ada (and more once you know it ;-).
Constraint_Error
This exception is raised when a constraint is exceeded, such constraintsinclude
Program_Error
This is raised by the run-time to mark an erroneous program event, such ascalling a procedure before package initialisation, or bad instantiation of ageneric package.
Storage_Error
This exception is raised when a call tonew could notbe satisfied due to lack of memory.
Tasking_Error
This is raised when problems occur during tasking rendezvous (see section7.2).
This is not a list of the predefined pragmas{L} what I have providedis the set of options to the pragmaSupress which can be used tostop certain run-time checks taking place. The pragma works from that point to the end of the innermost enclosing scope, or the end of the scope of the named object (see below).
Access_Check
RaisesConstraint_Error on dereference of anull access value.
Accessibility_Check
RaisesProgram_Error on access to inaccessible object or subprogram.
Discriminant_Check
RaisesConstraint_Error on access to incorrect component in adiscriminant record.
Division_Check
RaisesConstraint_Error on divide by zero.
Elaboration_Check
RaisesProgram_Error on unelaborated package or subprogrambody.
Index_Check
RaisesConstraint_Error on out of range array index.
Length_Check
RaisesConstraint_Error on array length violation.
Overflow_Check
RaisesConstraint_Error on overflow from numeric operation.
Range_Check
RaisesConstraint_Error on out of range scalar value.
Storage_Check
RaisesStorage_Error if not enough storage to satisfy anew call.
Tag_Check
RaisesConstraint_Error if object has an invalid tag for operation.
pragma Suppress(Access_Check);pragma Suppress(Access_Check, On => My_Type_Ptr);
The first use of the pragma above turns off checking fornullaccess values throughout the code (for the lifetime of the suppress), whereasthe second only suppresses the check for the named data item.

The point of this section is that by defaultall of these checks areenabled, and so any such errors will be trapped.

1.3.3 Unchecked programming.

You can subvert some of Adas type consistency by the use of unchecked programming. This is basically a set of procedures which do unsafe operations. These are:
Unchecked_Conversion
This generic function is defined as:
generictype Source (<>)is limited private;type Target (<>)is limited private;function Ada.Unchecked_Conversion (Source_Object : Source)return Target;
and should be instantiated like the example below (taken from one of the Ada-95standard library packagesAda.Interfaces.C).
function Character_To_charis new  Unchecked_Conversion (Character, char);
and can then be used to convert and Ada character to a C char, thus
A_Char : Interfaces.C.char := Character_To_char('a');
Unchecked_Deallocation
This generic function is defined as:
generictype Object (<>)is limited private;type Nameis access Object;procedure Ada.Unchecked_Deallocation (X :in out Name);
this function, instantiated with two parameters, only requires one foroperation,
type My_Typeis new Integer;type My_Ptris access My_Type;procedure Freeis new Unchecked_Deallocation (My_Type, My_Ptr);Thing : My_Ptr :=new My_Type;Free(Thing);

2 Ada Packages.{7}

Ada has one feature which many C/C++ programmers like to think they have anequivalent too - the package - they do not.

It is worth first looking at the role of header files in C/C++. Header filesare simply program text which by virtue of the preprocessor are inserted intothe compilers input stream. The#include directive knows nothingabout what it is including and can lead to all sorts of problems, such aspeople who#include "thing.c". This sharing of code by thepreprocessor lead to the#ifdef construct as you would have different interfaces for different people. The other problem is that C/C++ compilations can sometime take forever because a included b included c ... orthe near fatal a included a included a ...

Stroustrup has tried ref [9] (in vain, as far as I can see) to convince C++ programmersto remove dependance on the preprocessor but all the drawbacks are still there.

Any Ada package on the other hand consists of two parts, the specification (header) and body (code). The specification however is a completely stand aloneentity which can be compiled on its own and so must include specifications from other packages to do so. An Ada package body at compile time must refer to itspackage specification to ensure legal declarations, but in many Ada environmentsit would look up a compiled version of the specification.

The specification contains an explicit list of the visible components of a package and so there can be nointernal knowledge exploited as is oftenthe case in C code, ie module a contains a functions aa() but does not exportit through a header file, module b knows how a is coded and so uses theextern keyword to declare knowledge of it, and use it. C/C++programmers therefore have to mark private functions and data asstatic.

2.1 What a package looks like

Below is the skeleton of a package, spec and body.

--file example.ads, the package specification.package exampleis::end example;--file example.adb, the package body.package body exampleis::end example;

2.2 Include a package in another

Whereas a C file includes a header by simply inserting the text of the headerinto the current compilation stream with#include "example.h", theAda package specification has a two stage process.

Working with the example package above let us assume that we need to include another package, sayMy_Specs into this package so that it may be used. Firstly where do you insert it? Like C, package specifications can be inserted into either a specification or body depending on who is the client. Like a C header/code relationship any package included in the specification of package A is visible to the body of A, but not to clients of A. Each package is a seperate entity.

-- Specification for package examplewith Project_Specs;package exampleistype My_Typeis new Project_Spec.Their_Type;end example;-- Body for package examplewith My_Specs;package body exampleistype New_Type_1 is new My_Specs.Type_1;type New_Type_2 is new Project_Specs.Type_1;end example;

You can see here the basic visibility rules, the specification has to includeProject_Specs so that it can declareMy_Type. Thebody automatically inherits any packages included in its spec, so that youcan see that although the body does not includeProject_Specsthat package is used in the declaration ofNew_Type_1. The bodyalso includes another packageMy_Specs to declare the new typeNew_Type_2, the specification is unaware of this include and socannot useMy_Specs to declare new types. In a similar way anordinary client of the packageexample cannot use the inclusion ofProject_Specs, they would have to include it themselves.

To use an item, say a the typeType_1 you must name itMy_Specs.Type_1, in effect you have included the package name, not its contents. To get the same effect as the C#include you must also add another statement to make:

with My_Specs;use My_Specspackage body exampleis::end example;

It is usual in Ada to put the with and the use on the same line, for clarity. There is much more to be said about Ada packages, but that should be enough tostart with. There is a special form of theuse statementwhich can simply include an element (types only) from a package, consider:

use type Ada.Calendar.Time;

2.3 Package data hiding{7.3}

Data encapulation requires, for any level of safe reuse, a level of hiding. That is to say we need to defer the declaration of some data to a future point so that any client cannot depend on the structure of the data and allows the provider the ability to change that structure if the need arises.

In C this is done by presenting the 'private type' as avoid* which means that you cannot know anything about it, but implies that no one can do any form of type checking on it. In C++ we can forward declare classes and so provide an anonymous class type.

/* C code */typedef void* list;list create(void);// C++class Our_List {public:  Our_List(void);private:  class List_Rep;  List_Rep* Representation;};
You can see that as a C++ programmer you have the advantage that when writingthe implementation of Our_List and its internal representationList_Rep you have all the advantages of type checking, but the client still knows absolutely nothing about how the list is structured.

In Ada this concept is formalised into the 'private part' of a package. Thisprivate part is used to define items which are forward declared as private.

package Our_Lististype List_Repis private;function Createreturn List_Rep;privatetype List_Repisrecord       -- some dataend record;end Our_List;
As you can see the way the Ada private part is usually used the representationofList_Rep is exposed, but because it is a private type the only operations that the client may use are = and /=, all other operations must be provided by functions and procedures in the package.Note: we can even restrict use of = and /= by declaring the type aslimited private when you wish to have no predefined operators available.

You may not in the public part of the package specification declare variables of the private type as the representation is not yet known, we can declare constants of the type, but you must declare them in both places, forward reference them in the public part with no value, and then again in the privatepart to provide a value:

package Exampleistype Ais private;  B :constant A;privatetype Ais new Integer;  B :constant A := 0;end Example;
To get exactly the same result as the C++ code above then you must go one stepfurther, you must not expose the representation ofList_Rep, and so you might use:
package Our_Lististype List_Accessis limited private;function Createreturn List_Access;privatetype List_Rep; -- opaque typetype List_Accessis access List_Rep;end Our_List;
We now pass back to the client an access type, which points to a 'deferredincomplete type' whose representation is only required to be exposed in thepackage body.

2.4 Hierarchical packages.

Ada allows the nesting of packages within each other, this can be useful fora number of reasons. With Ada-83 this was possible by nesting package specsand bodies physically, thus:
package Outerispackage Inner_1isend Inner_1;package Inner_2isend Inner_2;privateend Outer;
Ada-95 has added to this the possibility to define child packages outside thephysical scope of a package, thus:
package Outerispackage Inner_1isend Inner_1;end Outer;package Outer.Inner_2isend Outer.Inner_2;
As you can seeInner_2 is still a child of outer but can be created at some later date, by a different team.

2.5 Renaming identifiers.

This is not a package specific topic, and it is only introduced here as theusing of packages is the most common place to find a renames clause.

Consider:

with Outer;with Outer.Inner_1;package New_Packageis  OI_1renames Outer.Inner_1;type New_typeis new OI_1.A_Type;end New_Package;
The use ofOI_1 not only saves us a lot of typing, but if outer were the packageSorting_Algorithms, andInner_1 wasInsertion_Sort, then we could haveSort renames Sorting_Algorithms.Insertion_Sort and then at somelater date if you decide that a quick sort is more approriate then you simplychange the renames clause, and the rest of the package spec stays exactly thesame.

Similarly if you want to include 2 functions from two different package with the same name then, rather than relying on overloading, or to clarify yourcode text you could:

with Package1;function Function1return Integerrenames Package1.Function;with Package2;function Function2return Integerrenames Package2.Function;
Another example of a renames clause is where you are using some complex structure and you want to in effect use a synonym for it during some processing. In the example below we have a device handler structure which contains some procedure types which we need to execute in turn. The first example contains a lot of text which we don't really care about, so the second removes most of it, thus leaving bare the real work we are attempting to do.
for devicein Device_Maploop  Device_Map(device).Device_Handler.Request_Device;  Device_Map(device).Device_Handler.Process_Function(Process_This_Request);  Device_Map(device).Device_Handler.Relinquish_Device;end loop;for devicein Device_Maploopdeclare    Device_Handler : Device_Typerenames      Device_Map(device).Device_Handler;begin    Device_Handler.Request_Device;    Device_Handler.Process_Function(Process_This_Request);    Device_Handler.Relinquish_Device;end;end loop;

3 Ada-95 Object Oriented Programming.

C++ extends C with the concept of aclass. A class is an extensionof the existingstruct construct which we have reviewed in section1.1.7 above. The difference with a class is that a class not only contains data (member attributes) but code as well (member functions). A class might look like:
class A_Device {public:  A_Device(char*, int, int);  char* Name(void);  int   Major(void);  int   Minor(void);protected:  char* name;  int   major;  int   minor;};
This defines a class called A_Device, which encapsulates a Unix-like /deventry. Such an entry has a name and a major and minor number, the actual dataitems are protected so a client cannot alter them, but the client can see them by calling the public interface functions.

The code above also introduces a constructor, a function with the same name asthe class which is called whenever the class is created. In C++ these may beoverloaded and are called either by thenew operator, or in localvariable declarations as below.

A_Device lp1("lp1", 10, 1);A_Device* lp1;  lp1 = new A_Device("lp1", 10, 1);
Creates a new device object calledlp1 and sets up the name andmajor/minor numbers.

Ada has also extended its equivalent of a struct, therecord butdoes not directly attach the member functions to it. First the Ada equivalentof the above class is

package Devicesistype Deviceis tagged private;type Device_Typeis access Device;function Create(Name  : String;                  Major : Integer;                  Minor : Integer)return Device_Type;function Name(this : Device_Type)return String;function Major(this : Device_Type)return Integer;function Minor(this : Device_Type)return Integer;privatetype Deviceis taggedrecord      Name  : String(1 .. 20);      Major : Integer;       Minor : Integer;end record;end Devices;
and the equivalent declaration of an object would be:
lp1 : Devices.Device_Type := Devices.Create("lp1", 10, 1);

3.1 The tagged type.

The addition of the keywordtagged to the definition of thetype Device makes it a class in C++ terms. The tagged type is simply an extension of the Ada-83 record type but (in the same way C++'sclassis an extension of C'sstruct) which includes a 'tag' which can identify not only its own type but its place in the type hierarchy.

The tag can be accessed by the attribute'Tag but should onlybe used for comparison, ie

dev1, dev2 : Device;if dev1'Tag = dev2'Tagthen
this can identify theisa relationship between two objects.

Another important attribute'Class exists which is used in typedeclarations to denote theclass-wide type, the inheritence tree rootedat that type, ie

type Device_Classis Device'Class;-- or more normallytype Device_Classis access Device'Class;
The second type denotes a pointer to objects of typeDevice andany objects whos type has been inherited fromDevice.

3.2 Class member attributes.

Member attributes in C++ directly map onto data members of the tagged type. Sothechar* name directly maps intoName : String.

3.3 Class member functions.

Non-virtual, non-const, non-static member functions map onto subprograms, withinthe same package as the tagged type, whos first parameter is of that tagged typeor an access to the tagged type, or who returns such a type.

3.4 Virtual member functions.

Virtual member functions map onto subprograms, within the same package as the tagged type, whos first parameter is of the class-wide type, or an access tothe class-wide type, or who returns such a type.

A pure virtual function maps onto a virtual member function with the keywordsis abstract before the semicolon. When any pure virtualmember functions exist the tagged type they refer to must also be identifiedas abstract. Also, if an abstract tagged type has been introduced which hasno data, then the following shorthand can be used:

type Root_Typeis abstract tagged null record;

3.5 Static members.

Static members map onto subprograms within the same packageas the tagged type. These are no different from normal Ada-83 subprograms, itis up to the programmer when applying coding rules to identify only memberfunctions or static functions in a package which includes a tagged type.

3.6 Constructors/Destructors for Ada.

As you can see from the example above there is no constructors and destructorsin Ada. In the example above we have synthesised this with theCreate function which creates a new object and returns it. If you intend to use this method then the most important thing to remember is touse the same name throughout,Create Copy Destroy etc are all useful conventions.

Ada does provide a library packageAda.Finalization which canprovide constructor/destructor like facilities for tagged types.
Note: See ref 6.

3.7 Inheritance, single and multiple.

The most common attribute sited as the mark of a true object oriented languageis support for inheritance. Ada-95 adds this astagged type extension.

For example, let us now inherit the device type above to make a tape device,firstly in C++

class A_Tape : public A_Device {public:  A_Tape(char*, int, int);  int Block_Size(void);protected:  int block_size;};
Now let us look at the example in Ada.
package Device.Tapesistype Tapeis new devicewith private;type Tape_Typeis access Tape;function Create(Name  : String;                  Major : Integer;                  Minor : Integer)return Tape_Type;function Block_Size(this : Tape_Type)return Integer;privatetype Tapeis new Devicewithrecord      Block_Size : Integer;end record;end Device.Tapes;
Ada does not directly support multiple inheritance, ref [7] has an example ofhow to synthesise mulitple inheritance.

3.8 public/protected/private.

In the example at the top of this section we provided theDevicecomparison. In this example the C++ class provided a public interface and a protected one, the Ada equivalent then provided an interface in the public part and the tagged type declaration in the private part. Because of the rulesfor child packages (see2.4) a child of theDevices package can see the private part and so can use the definition of theDevice tagged type.

Top mimic C++ private interfaces you can choose to use the method above, whichin effect makes them protected, or you can make them really private by usingopaque types (see2.3).

3.9 A more complete example.

class base_device {public:  char*   name(void) const;  int     major(void) const;  int     minor(void) const;  enum { block, character, special } io_type;  io_type type(void) const;  char   read(void) = 0;  void   write(char) = 0;    static char* type_name(void);protected:  char*   _name;  int     _major;  int     _minor;  static const io_type _type;  base_device(void);private:  int     _device_count;};
The class above shows off a number of C++ features,All of these, including the reasons why they might be used should be familiarto you, below is an equivalent specification in Ada.
package Devicesistype Deviceis abstract tagged limited private;type Device_Typeis access Device;type Device_Classis access Device'Class;type IO_Typeis (Block, Char, Special);function Name(this : in Device_Type)return String;function Major(this : in Device_Type)return Integer;function Minor(this : in Device_Type)return Integer;function IOType(this : in Device_Type)return IO_Type;function  Read(this : Device_Class)return Characteris abstract;procedure Write(this : Device_Class; Output : Character)is abstractfunction Type_Name return String;privatetype Device_Count;type Device_Privateis access Device_Count;type Deviceis abstract tagged limitedrecord      Name  : String(1 .. 20);      Major : Integer;       Minor : Integer;      Count : Device_Private;end record;  Const_IO_Type   :constant IO_Type := special;  Const_Type_Name :constant String  := "Device";end Devices;

4 Generics

One of Ada's strongest claims is the ability to code for reuse. C++ also claimsreuse as one of its goals through Object Oriented Programming. Ada-83 allowedyou to manage the data encapsulation and layering through the package mechanismand Ada-95 does include proper facilities for OO Programming. Where Ada ledhowever, and C++ is following is the area of generic, or template programming.

4.1 A generic procedure{12.6}

For example. A sort algorithm is well understood, and we may like to code asort for an array of int's in C, we would have a function like:
void sort(int *array, int num_elements);
however when you come to sort an array of structures you either have to rewritethe function, or you end up with a generic sort function which looks like this:
void sort(void *array, int element_size, int element_count,           int (*compare)(void* el1, void *el2));
This takes a bland address for the start of the array user supplied parametersfor the size of each element and the number of elements and a function whichcompares two elements. C does not have strong typing, but you have just strippedaway any help the compiler might be able to give you by usingvoid*.

Now let us consider an Ada generic version of the sort function:

generictype index_typeis (<>);type element_typeis private;type element_arrayis array (index_typerange<>)of element_type;with function "<" (el1, el2 : element_type)return Boolean;procedure Sort(the_array :in out element_array);
This shows us quite a few features of Ada generics and is a nice place to start,for example note that we have specified a lot of detail about the thing we are going to sort, it is an array, for which we don't know the bounds so it is specified asrange<>. We also can't expect that the range is aninteger range and so we must also make the range type a parameter,index_type. Then we come onto the element type, this is simply specifiedas private, so all we know is that we can test equality and assign one to another. Now that we have specified exactly what it is we are going to sort wemust ask for a function to compare two elements, similar to C we must ask theuser to supply a function, however in this case we can ask for an operator function and notice that we use the keywordwith beforethe function.

I think that you should be able to see the difference between the Ada code andC code as far as readability (and therefore maintainability) are concerned andwhy, therefore, Ada promotes the reuse philosophy.

Now let's use our generic to sort some ofMyTypes.

MyArray :array (Integer 0 .. 100)of MyType;function LessThan(el1, el2 : MyType)return Boolean;procedure SortMyTypeis new Sort(Integer, MyType, MyArray, LessThan);SortMyType(MyArray);
The first two lines simply declare the array we are going to sort and a littlefunction which we use to compare two elements (note: no self respecting Ada programmer would define a functionLessThan when they can use"<", this is simply for this example).

We then go on to instantiate the generic procedure and declare that we have anarray calledMyArray of typeMyType using anInteger range and we have a function to compare two elements.Now that the compiler has instantiated the generic we can simply call it usingthe new name.

Note: The Ada compiler instantiates the generic and will ensure type safetythroughout.

4.2 Generic packages{12.7}

Ada packages and generics where designed to go together, you will even findgeneric packages in the Ada standard library. For example:
generictype Element_Typeis private;package Ada.Direct_IOis
Is the standard method for writing out binary data structures, and so one could write out to a file:
type My_Structisrecord    ...end record;package My_Struct_IOis new Ada.Direct_IO(My_Struct);use My_Struct_IO;Item : My_Struct;File : My_Struct_IO;...My_Struct_IO.Write(File, Item);
Note: see section5.2 for a more detailed study of thesepackages and how they are used.

4.3 Generic types and other parameters{12.4}

The types you may specify for a generic subprogram or package are as follows:
type Xis private
We can know nothing about the type, except that we may test for equality and we may assign one to another. If we add in the keywordlimited then even these abilities are unavailable.
type X(<>)is private
Added for Ada-95, this is similar to the parameter above except that we can define data items in the body of our package of type X, this may be illegal if the type passed is unconstrained, ieString. Ada-95 does not allow the instantiation of generics with unconstrained types, unless you use this syntax in which case you cannot declare data items of this type.
type Xis (<>)
The type is a discrete type, Integer, Character, Enumeration etc.
type Xis range <>
The type indicates a range, ie0 .. 100.
type Xis mod <>
The type is a modulus type of unknown size (Added for Ada-95).
type Xis digits <>
The type is a floating point type.
type Xis delta <>
The type is a fixed point type.
type Xis tagged private
The type is a tagged type, ie an Ada-95 extensible record.
There is one final parameter which may be passed to a generic package, another generic package (Added for Ada-95).
with Generic_Tree;genericwith package A_Treeis new Generic_Tree(<>);package Tree_Walkeris  -- some code.end Tree_Walker;
This says that we have some package called Generic_Tree which is a generic package implementing a tree of generic items. We want to be able to walk anysuch tree and so we say that we have a new generic package which takes a parameter which must be an instantiated package. ie
package ASTis new Generic_Tree(Syntax_Element);package AST_Printis new Tree_Walker(AST);

5 IO

A common area for confusion is the Ada IO model, this has been shaped by thenature of the language itself and specifically the strong typing which has adirect impact on the model used to construct the IO libraries. If you stop and think about it briefly it is quite clear that with the typing rules we have introduced above you cannot write a function like the Cwrite()which takes any old thing and puts it out to a file, how can you write a function which will take any parameter, even types which will be introducedafter it has been completed. Ada-83 took a two pronged approach to IO, with the packageText_IO for simple, textual input output, and the packagesSequential_IO andDirect_IO which aregeneric packages for binary output of structured data.

The most common problem for C and C++ programmers is the lack of the printf family of IO functions. There is a good reason for their absence in Ada, the use in C of variable arguments, the '...' at the end of the printf function spec. Ada cannot support such a construct as the type of each parameter is unknown.

5.1 Ada.Text_IO

The common way to do console-like IO, similar to C's printf(), puts() andputchar() is to use the packageAda.Text_IO. This provides a set of overloaded functions calledPut andGet to read and write to the screen or to simple text files. There are also functions to open and close such files, check end of file conditions and to do line and page management.

A simple program below usesText_IO to print a message to thescreen, including numerics! These are achieved by using the types attribute'Image which gives back a String representation of a value.

with Ada.Text_IO;use Ada.Text_IO;procedure Test_IOisbegin  Put_Line("Test Starts Here >");  Put_Line("Integer is " & Integer'Image(2));  Put_Line("Float is   " & Float'Image(2.0));  Put_Line("Test Ends Here");end Test_IO;
It is also possible to use one of the generic child packages ofAda.Text_IO such asAda.Text_IO.Integer_IO which can be instantiated with a particular type to provide type safe textual IO.
with Ada.Text_IO;type My_Integeris new Integer;package My_Integer_IOis new Ada.Text_IO.Integer_IO(My_Integer);use My_Integer_IO;

5.2 Ada.Sequential_IO and Ada.Direct_IO

These two generic packages provide IO facilities for files which contain identical records. They can be instantiated in a similar way to the generictext IO packages above, so for example:
with Ada.Direct_IO;package A_Databaseistype File_Headerisrecord      Magic_Number: Special_Stamp;      Number_Of_Records: Record_Number;      First_Deleted: Record_Number;end record;type Rowisrecord      Key: String(1 .. 80);      Data: String(1 .. 255);end record;package Header_IOis new Direct_IO (File_Header);use Header_IO;package Row_IOis new Direct_IO (Row);use Record_IO;end A_Database;
Now that we have some instantiated packages we can read and write records andheaders to and from a file. However we want each database file to consist ofa header followed by a number of rows, so we try the following
declare  Handle   : Header_IO.File_Type;  A_Header : File_Header;  A_Row    : Row;begin  Header_IO.Open(File => Handle, Name => "Test");  Header_IO.Write(Handle, A_Header);  Row_IO.Write(Handle, A_Row);  Header_IO.Close(Handle);end;
The obvious error is thatHandle is defined as a type exported from theHeader_IO package and so cannot be passed to the procedureWrite from the packageRow_IO. This strong typingmeans that bothSequential_IO andDirect_IO are designed only to work on files containg all elements of the same type.

When designing a package, if you want to avoid this sort of problem (the designersof these packages did intend this restriction) then embed the generic partwithin an enclosing package, thus

package generic_IOistype File_Typeis limited private;procedure Create(File : File_Type ....procedure Close .....generic     Element_Typeis private;package Read_Writeisprocedure Read(File : File_Type;                   Element : Element_Type ...procedure Write .....end Read_Write;end generic_IO;
Which would make our database package look something like
with generic_IO;package A_Databaseistype File_Headerisrecord      Magic_Number: Special_Stamp;      Number_Of_Records: Record_Number;      First_Deleted: Record_Number;end record;type Rowisrecord      Key: String(1 .. 80);      Data: String(1 .. 255);end record;package Header_IOis new generic_IO.Read_Write (File_Header);use Header_IO;package Row_IOis new generic_IO.Read_Write (Row);use Record_IO;end A_Database;  :  :declare  Handle   : generic_IO.File_Type;  A_Header : File_Header;  A_Row    : Row;begin  generic_IO.Open(File => Handle, Name => "Test");  Header_IO.Write(Handle, A_Header);  Row_IO.Write(Handle, A_Row);  generic_IO.Close(Handle);end;

5.3 Streams

This is a new Ada-95 feature which I will add once I have a copy of GNAT whichsupports the feature. I like to have examples which I have compiled/tried.

6 Interfacing to other languages

Ada-95 has a specified set of packages under the top level packageInterfaces which define functions to allow you to convert data typesbetween the Ada program and the external language routines.

The full set of packages defined for interfaces are show below.

Interfaces
C
Pointers
Strings
COBOL
CPP
Fortran

7 Concurrency

To some this section does not fit in the remit of a C++ programmers guide to Ada, however most modern operating systems contain constructs known either aslightweight processes or asthreads. These allow programmers to have multiple threads of execution within the same address space. Many of you will be familiar with this concept and so I will use it as a basis for explaining tasks below, you may skip the next paragraph.

Unlike C/C++ Ada defines a concurrency model as part of the language itself.Some languages (Modula-3) provide a concurrency model through the use of standard library packages, and of course some operating systems provide libraries to provide concurrency. In Ada there are two base components, the task which encapsulates a concurrent process and the protected type which is a data structure which provides guarded access to its data.

7.1 Tasks

7.1.1 Tasks as threads

For those who have not worked in a multi-threaded environment you might liketo consider the advantages. In a non-multi-threaded UNIX (for example) thegranularity of concurrency is the process. This process is an atomic entityto communicate with other processes you must use sockets, IPC etc. The onlyway to start a cooperating process is to initialise some global data anduse thefork function to start a process which is a copy of thecurrent process and so inherits these global variables. The problem with thismodel is that the global variables are now replicated in both processes, achange to one is not reflected in the other.

In a multi-threaded environment multiple concurrent processes are allowed within the same address space, that is they can share global data. Usuallythere are a set of API calls such asStartThread, StopThreadetc which manage these processes.

Note: An Ada program with no tasks is really an Ada process with a single running task, the default code.

7.1.2 A Simple task

In the example below an Ada task is presented which will act like a threadfound in a multi-threaded operating system such as OS/2, Windows-NT orSolaris.
task Xisend X;task body Xisbeginloop    -- processing.end loop;end X;
As with packages a task comes in two blocks, the specification and the body.Both of these are shown above, the task specification simply declares thename of the task and nothing more. The body of the task shows that it isa loop processing something. In many cases a task is simply a straightthrough block of code which is executed in parallel, or it may be, as inthis case, modelled as a service loop.

7.1.3 Task as types

Tasks can be defined as types, this means that you can define a task whichcan be used by any client. Once defined as a task objects of that type canbe created in the usual way. Consider:
task type Xisend X;Item : X;Items :array (0 .. 9)of X;
Note: however that tasks are declared as constants, you cannot assign to them and you cannot test for equality.

7.2 Task synchronization (Rendezvouz)

The advantage of Ada tasking is that the Ada task model provides much morethan the multi-threaded operating systems mentioned above. When creatinga thread to do some work we must seperately create semaphores and/or other IPC objects to manage the cooperation between threads, and all ofthis is of course system dependant.

The Ada tasking model defines methods for inter-task cooperation and muchmore in a system independant way using constructs known asRendezvous.

A Rendezvouz is just what it sounds like, a meeting place where two tasksarrange to meet up, if one task reaches it first then it waits for the other to arrive. And in fact a queue is formed for each rendezvous of alltasks waiting (in FIFO order).

7.2.1 entry/accept

A task contains a number of elements, data items, procedural code andrendezvous. A rendezvous is represented in the task specification likea procedure call returning no value (though it can havein outparameters). It can take any number of parameters, but rather that the keywordprocedure thekeywordentry is used. In the task body however thekeywordaccept is used, and instead of the proceduresyntax ofis begin simplydo is used. Thereason for this is that rendezvous in a task are simply sections of the code in it, they are not seperate elements as procedures are.

Consider the example below, a system of some sort has a cache of elements, it requests an element from the cache, if it is not in the cache then the cache itself reads an element from the master set. If this process of reading from the master fills the cache then it must be reordered.When the process finishes with the item it callsPutBack which updates the cache and if required updates the master.

task type Cached_Itemsisentry Request(Item :out Item_Type);entry PutBack(Item :in  Item_Type);end Cached_Items;task body Cached_Itemsis  Log_File : Ada.Text_IO.File_Type;begin  -- open the log file.loopaccept Request(Item :out Item_Type)do      -- satisfy from cache or get new.end Request;    -- if had to get new, then quickly    -- check cache for overflow.accept PutBack(Item :in  Item_Type)do      -- replace item in cache.end PutBack;    -- if item put back has changed    -- then possibly update original.end loop;end Cached_Items;-- the client code begins here:declare  Cache : Cached_Items;  Item  : Item_Type;begin  Cache.Request(Item);  -- process.  Cache.PutBack(Item);end;
It is the sequence of processing which is important here, Firstly the client task (remember, even if the client is the main program it is still, logically, a task) creates the cache task which executes its body. The first thingthe cache (owner task) does is some procedural code, its initialisation, in this case to open its log file. Next we have an accept statement, this is a rendezvous, and in this case the two parties are the owner task, when it reaches the keywordaccept and the client task that callsCache.Request(Item).

If the client task callsRequest before the owner task has reached theaccept then the client task will wait for the owner task. However we would not expect the owner task to take very long to open a log file,so it is more likely that it will reach theaccept first andwait for a client task.

When both client and owner tasks are at the rendezvous then the owner task executes theaccept code while the client task waits. When the ownertask reaches the end of the rendezvous both the owner and the client are set offagain on their own way.

7.2.2 select

If we look closely at our example above you might notice that if the client task callsRequest twice in a row then you have a deadly embrace, the owner task cannot get toRequest before executingPutBack and the client task cannot executePutBack until it has satisfied the second call toRequest.

To get around this problem we use aselect statement which allows the task to specify a number of entry points which are valid at any time.

task body Cached_Itemsis  Log_File : Ada.Text_IO.File_Type;begin  -- open the log file.accept Request(Item : Item_Type)do  -- satisfy from cache or get new.end Request;loopselectaccept PutBack(Item : Item_Type)do        -- replace item in cache.end PutBack;      -- if item put back has changed      -- then possibly update original.oraccept Request(Item : Item_Type)do        -- satisfy from cache or get new.end Request;      -- if had to get new, then quickly      -- check cache for overflow.end select;end loop;end Cached_Items;
We have done two major things, first we have added theselect construct which says that during the loop a client may call either of the entry points. The second point is that we moved a copy of the entry point into the initialisation section of the task so that we must callRequest before anything else. It is worth noting that we can have many entry points with thesame name and they may be the same or may do something different but we only needoneentry in the task specification.

In effect the addition of theselect statement means thatthe owner task now waits on theselect itself until oneof the specifiedaccepts are called.

Note: possibly more important is the fact that we have not changed the specification for the task at all yet!.

7.2.3 guarded entries

Within a select statement it is possible to specify the conditions under whichanaccept may be valid, so:
task body Cached_Itemsis  Log_File : Ada.Text_IO.File_Type;  Number_Requested : Integer := 0;  Cache_Size :constant Integer := 50;begin  -- open the log file.accept Request(Item : Item_Type)do  -- satisfy from cache or get new.end Request;loopselectwhen Number_Requested > 0 =>accept PutBack(Item : Item_Type)do        -- replace item in cache.end PutBack;      -- if item put back has changed      -- then possibly update original.oraccept Request(Item : Item_Type)do        -- satisfy from cache or get new.end Request;      -- if had to get new, then quickly      -- check cache for overflow.end select;end loop;end Cached_Items;
This (possibly erroneous) example adds two internal values, one to keep trackof the number of items in the cache, and the size of the cache. If no itemshave been read into the cache then you cannot logicaly put anything back.

7.2.4 delays

It is possible to put adelay statement into a task, thisstatement has two modes, delay for a given amount of time, or delay until agiven time. So:
delay 5.0;                -- delay for 5 secondsdelay Ada.Calendar.Clock; -- delay until it is ...delay until A_Time;       -- Ada-95 equivalent of above
The first line is simple, delay the task for a given number, or fraction of, seconds. This mode takes a parameter of typeDuration specifiedin the packageSystem. The next two both wait until a time isreached, the secodn line also takes aDuration, the third line takes a parameter of typeTime from packageAda.Calendar.

It is more interesting to note the effect of one of these when used in a selectstatement. For example, if anaccept is likely to take a long time you might use:

selectaccept An_Entrydoend An_Entry;ordelay 5.0;  Put("An_Entry: timeout");end select;
This runs thedelay and theaccept concurrently and if thedelay completes before theaccept then theaccept is abortedand the task continues at the statement after thedelay,in this case the error message.

It is possible to protect procedural code in the same way, so we might amendour example by:

task body Cached_Itemsis  Log_File : Ada.Text_IO.File_Type;  Number_Requested : Integer := 0;  Cache_Size :constant Integer := 50;begin  -- open the log file.accept Request(Item : Item_Type)do  -- satisfy from cache or get new.end Request;loopselectwhen Number_Requested > 0 =>accept PutBack(Item : Item_Type)do        -- replace item in cache.end PutBack;select        -- if item put back has changed        -- then possibly update original.ordelay 2.0;        -- abort the cache update codeend select;oraccept Request(Item : Item_Type)do        -- satisfy from cache or get new.end Request;      -- if had to get new, then quickly      -- check cache for overflow.end select;end loop;end Cached_Items;

7.2.5 select else

Theelse clause allows us to execute a non-blockingselect statement, so we could code a polling task, suchas:

selectaccept Do_Somethingdoend DO_Something;else  -- do something else.end select;
So that if no one has called the entry points specified we continue rather thanwaiting for a client.

7.2.6 termination

The example we have been working on does not end, it simply loops forever. Wecan terminate a task by using the keywordterminate whichexecutes a nice orderly cleanup of the task. (We can also kill a task in a moreimmediate way using theabort command, this isNOTrecommended).

Theterminate alternative is used for a task to specifythat the run time environment can terminate the task if all its actions arecomplete and no clients are waiting.

loopselectaccept Do_Somethingdoend Do_Something;orterminate;end select;end loop;
Theabort command is used by a client to terminate a task,possibly if it is not behaving correctly. The command takes a task identiferas an argument, so using our example above we might say:
if Task_In_Error(Cache)thenabort Cache;end if;
Thethen abort clause is very similar to thedelay example above, the code betweenthen abortandend select is aborted if thedelay clause finishes first.
selectdelay 5.0;  Put("An_Entry: timeout");then abortaccept An_Entrydoend An_Entry;end select;

7.2.7 conditional entry calls

In addition to direct calls to entry points clients may rendezvous with a task with three conditional forms of a select statement:
Timed entry call
Conditional entry call
Asynchronous select

7.3 Protected types

Protected types are a new feature added to the Ada-95 language standard. These act like the monitor constructs found in other languages, which means that theymonitor access to their internal data and ensure that no two tasks can accessthe object at the same time. In effect every entry point is mutually exclusive. Basically a protected type looks like:
protected type Cached_Itemsisfunction Requestreturn Item_Type;procedure PutBack(Item :in Item_Type);private  Log_File : Ada.Text_IO.File_Type;  Number_Requested : Integer := 0;  Cache_Size :constant Integer := 50;end Cached_Items;protected body Cached_Itemsisfunction Requestreturn Item_Typeisbegin    -- initialise, if required    -- satisfy from cache or get new.    -- if had to get new, then quickly    -- check cache for overflow.end Request;procedure PutBack(Item :in Item_Type)isbegin    -- initialise, if required    -- replace item in cache.    -- if item put back has changed    -- then possibly update original.end Request;end Cached_Items;
This is an implementation of our cache from the task discussion above. Notenow that the namesRequest andPutBack are nowsimply calls like any other. This does show some of the differences betweentasks and protected types, for example the protected type above, because itis a passive object cannot completly initialise itself, so each procedure and/or function must check if it has been initialised. Also we must do allprocessing within the stated procedures.

References

1 Ada Language Reference Manual
2 Ada Rationale
3 Ada Quality and Style: Guidelines for professional programmers
4 Programming in Ada (3rd Edition), J.G.P.Barnes, Addison Wesley.
5 Ada Programmers FAQ.
6 Abstract Data Types Are Under Full Control with Ada9X (TRI-Ada '94)
7 Working With Ada9X Classes (TRI-Ada '94)
8 Lovelace on-line tutorial.
9 Design and evolution of C++, Bjarne Stroustrup, Addison Wesley.
10 The annotated C++ reference manual, Margaret Ellis and Bjarne Stroustrup, Addison Wesley.

Acknowledgements

My thanks must go to the following people for help, pointers, proof-reading,suggestions and encouragement.
S. Tucker Taft (Intermetrics), Magnus Kempe and Robb Nebbe (EPFL),Michael Bartz (University of Memphis),David Weller,Kevin Nash (Bournemouth University),Laurent Guerby,Jono Powell,Bill Wagner (Hazeltine).
Once again, thank you.
HBAPFor comments, additions, corrections, gripes, kudos, etc. e-mail to: Simon Johnston (Team Ada) --skj@rb.icl.co.uk
ICL Retail Systems
Page last modified: $Date: 1995/07/17 08:43:31 $
[8]ページ先頭

©2009-2025 Movatter.jp