❌ About FreshRSS

Normal view

There are new articles available, click to refresh the page.
Before yesterdaySoftware Orthodoxy

Ada vs C++ Bit-fields

Uses of Bit-fields
Bit-fields are typically used to implement communication protocols and to create device drivers for actuators or sensors. Frequently the actuators or sensors will use A/D and D/A converters to associate specific bit groupings with analog voltages on device connector pins.
In safety critical systems the bit patterns of the bit-fields must be correctly arranged or communication with the actuator or sensor will be incorrect. Since many safety critical systems require command of actuators, combined with resulting sensor readings to provide a closed loop control system, the ability to correctly define the bit layout for each device interface is highly safety critical.

JSF C++ Coding Standard

AV Rule 154 (MISRA Rules 111 and 112, Revised)

Bit-fields shall have explicitly unsigned integral or enumeration types only.



Rationale: Whether a plain (neither explicitly signed nor unsigned) char, short, int or long bit-field is signed or unsigned is implementation-defined.[10] Thus, explicitly declaring a bit-filed unsigned prevents unexpected sign extension or overflow.
Note: MISRA Rule 112 no longer applies since it discusses a two-bit minimum-length requirement for bit-fields of signed types.

AV Rule 155

Bit-fields will not be used to pack data into a word for the sole purpose of saving space.
Note: Bit-packing should be reserved for use in interfacing to hardware or conformance to communication protocols.
Warning: Certain aspects of bit-field manipulation are implementation-defined.


Rationale: Bit-packing adds additional complexity to the source code. Moreover, bit-packing may not save any space at all since the reduction in data size achieved through packing is often offset by the increase in the number of instructions required to pack/unpack the data.

AV Rule 156  (MISRA Rule 113)

All the members of a structure (or class) shall be named and shall only be accessed via their names.

Rationale: Reading/writing to unnamed locations in memory is error prone.
Exception: An unnamed bit-field of width zero may be used to specify alignment of the next bit-field at an allocation boundary. [10], 9.6(2)



C++ Bit-fields Explained

The following explanation of C++ bit-fields is taken from http://en.cppreference.com/w/cpp/language/bit_field

Bit field

Declares a class data member with explicit size, in bits. Adjacent bit field members may be packed to share and straddle the individual bytes.
A bit field declaration is a class data member declaration which uses the following declarator:
identifier(optional)attr(optional) : size
(1)
The type of the bit field is introduced by the decl-specifier-seq of the declaration syntax.
attr(C++11)
-
optional sequence of any number of attributes
identifier
-
the name of the bit field that is being declared. The name is optional: nameless bitfields introduce the specified number of bits of padding
size
-
an integral constant expression with a value greater or equal to zero. When greater than zero, this is the number of bits that this bit field will occupy. The value zero is only allowed for nameless bitfields and has special meaning: it specifies that the next bit field in the class definition will begin at an allocation unit's boundary.

Explanation

The number of bits in a bit field sets the limit to the range of values it can hold:
#include <iostream>
struct S {
// three-bit unsigned field,
// allowed values are 0...7

};
int main()
{
S s
s.b
std::cout << s.b << '\n'; // output: 0
}
Multiple adjacent bit fields are usually packed together (although this behavior is implementation-defined):
#include <iostream>
struct S {
// will usually occupy 2 bytes:
// 3 bits: value of b1
// 2 bits: unused
// 6 bits: value of b2
// 2 bits: value of b3
// 3 bits: unused

};
int main()
{
std::cout << sizeof(S) << '\n'; // usually prints 2
}
The special unnamed bit field of size zero can be forced to break up padding. It specifies that the next bit field begins at the beginning of its allocation unit:
#include <iostream>
struct S {
// will usually occupy 2 bytes:
// 3 bits: value of b1
// 5 bits: unused
// 6 bits: value of b2
// 2 bits: value of b3




};
int main()
{
std::cout << sizeof(S) << '\n'; // usually prints 2
}
If the specified size of the bit field is greater than the size of its type, the value is limited by the type: a std::uint8_t b : 1000; would still hold values between 0 and 255. the extra bits become unused padding.
Because bit fields do not necessarily begin at the beginning of a byte, address of a bit field cannot be taken. Pointers and non-const references to bit fields are not possible. When initializing a const reference from a bit field, a temporary is created (its type is the type of the bit field), copy initialized with the value of the bit field, and the reference is bound to that temporary.
The type of a bit field can only be integral or enumeration type.
A bit field cannot be a static data member.

Notes

The following properties of bit fields are implementation-defined
  • Everything about the actual allocation details of bit fields within the class object
  • For example, on some platforms, bit fields don't straddle bytes, on others they do
  • Also, on some platforms, bit fields are packed left-to-right, on others right-to-left
  • Whether char, short, int, long, and long long bit fields that aren't explicitly signed or unsigned are signed or unsigned.
  • For example, int b:3; may have the range of values 0..7 or -4..3.
(until C++14)

References

  • C++11 standard (ISO/IEC 14882:2011):
  • 9.6 Bit-fields [class.bit]
  • C++98 standard (ISO/IEC 14882:1998):
  • 9.6 Bit-fields [class.bit]


Ada Alternative

Ada allows the programmer to define bit layouts within a record with much more confidence than can be done by the C++ programmer. The Ada 2012 Reference Manual describes the use of record representation clauses.

13.5.1 Record Representation Clauses

1
record_representation_clause specifies the storage representation of records and record extensions, that is, the order, position, and size of components (including discriminants, if any).

Syntax

2
record_representation_clause ::= 
    
for first_subtype_local_name use
      
record [mod_clause]
        {
component_clause}
      
end record;
3
component_clause ::= 
    
component_local_name at position range first_bit .. last_bit;
4
position ::= static_expression
5
first_bit ::= static_simple_expression
6
last_bit ::= static_simple_expression

Name Resolution Rules

7
Each positionfirst_bit, and last_bit is expected to be of any integer type. 

Legality Rules

8/2
The first_subtype_local_name of a record_representation_clause shall denote a specific record or record extension subtype. 
9
If the component_local_name is a direct_name, the local_name shall denote a component of the type. For a record extension, the component shall not be inherited, and shall not be a discriminant that corresponds to a discriminant of the parent type. If the component_local_name has an attribute_designator, the direct_name of the local_name shall denote either the declaration of the type or a component of the type, and theattribute_designator shall denote an implementation-defined implicit component of the type.
10
The positionfirst_bit, and last_bit shall be static expressions. The value of position and first_bit shall be nonnegative. The value of last_bit shall be no less than first_bit – 1. 
10.1/2
   If the nondefault bit ordering applies to the type, then either: 
10.2/2
the value of last_bit shall be less than the size of the largest machine scalar; or
10.3/2
the value of first_bit shall be zero and the value of last_bit + 1 shall be a multiple of System.Storage_Unit. 
11
At most one component_clause is allowed for each component of the type, including for each discriminant (component_clauses may be given for some, all, or none of the components). Storage places within acomponent_list shall not overlap, unless they are for components in distinct variants of the same variant_part.
12
A name that denotes a component of a type is not allowed within a record_representation_clause for the type, except as the component_local_name of a component_clause.

Static Semantics

13/2
 record_representation_clause (without the mod_clause) specifies the layout.
13.1/2
   If the default bit ordering applies to the type, the positionfirst_bit, and last_bit of each component_clause directly specify the position and size of the corresponding component.
13.2/3
   If the nondefault bit ordering applies to the type, then the layout is determined as follows:
13.3/2
the component_clauses for which the value of last_bit is greater than or equal to the size of the largest machine scalar directly specify the position and size of the corresponding component;
13.4/2
for other component_clauses, all of the components having the same value of position are considered to be part of a single machine scalar, located at that position; this machine scalar has a size which is the smallest machine scalar size larger than the largest last_bit for all component_clauses at that position; the first_bit and last_bit of each component_clause are then interpreted as bit offsets in this machine scalar. 
14
record_representation_clause for a record extension does not override the layout of the parent part; if the layout was specified for the parent type, it is inherited by the record extension. 

Implementation Permissions

15
An implementation may generate implementation-defined components (for example, one containing the offset of another component). An implementation may generate names that denote such implementation-defined components; such names shall be implementation-defined attribute_references. An implementation may allow such implementation-defined names to be used in record_representation_clauses. An implementation can restrict such component_clauses in any manner it sees fit. 
16
If a record_representation_clause is given for an untagged derived type, the storage place attributes for all of the components of the derived type may differ from those of the corresponding components of the parent type, even for components whose storage place is not specified explicitly in the record_representation_clause.

Implementation Advice

17
The recommended level of support for record_representation_clauses is: 
17.1/2
An implementation should support machine scalars that correspond to all of the integer, floating point, and address formats supported by the machine.
18
An implementation should support storage places that can be extracted with a load, mask, shift sequence of machine code, and set with a load, shift, mask, store sequence, given the available machine instructions and run-time model.
19
A storage place should be supported if its size is equal to the Size of the component subtype, and it starts and ends on a boundary that obeys the Alignment of the component subtype.
20/2
For a component with a subtype whose Size is less than the word size, any storage place that does not cross an aligned word boundary should be supported.
21
An implementation may reserve a storage place for the tag field of a tagged type, and disallow other components from overlapping that place. 
22
An implementation need not support a component_clause for a component of an extension part if the storage place is not after the storage places of all components of the parent type, whether or not those storage places had been specified. 
NOTES
23
14  If no component_clause is given for a component, then the choice of the storage place for the component is left to the implementation. If component_clauses are given for all components, the record_representation_clause completely specifies the representation of the type and will be obeyed exactly by the implementation. 

Examples

24
Example of specifying the layout of a record type: 
25
Word : constant := 4;  --  storage element is byte, 4 bytes per word
26
type State         is (A,M,W,P);
type Mode          is (Fix, Dec, Exp, Signif);
27
type Byte_Mask     is array (0..7)  of Boolean;
type State_Mask    is array (State) of Boolean;
type Mode_Mask     is array (Mode)  of Boolean;
28
type Program_Status_Word is
  record
      System_Mask        : Byte_Mask;
      Protection_Key     : Integer range 0 .. 3;
      Machine_State      : State_Mask;
      Interrupt_Cause    : Interruption_Code;
      Ilc                : Integer range 0 .. 3;
      Cc                 : Integer range 0 .. 3;
      Program_Mask       : Mode_Mask;
      Inst_Address       : Address;
end record;
29
for Program_Status_Word use
  record
      System_Mask      at 0*Word range 0  .. 7;
      Protection_Key   at 0*Word range 10 .. 11; -- bits 8,9 unused
      Machine_State    at 0*Word range 12 .. 15;
      Interrupt_Cause  at 0*Word range 16 .. 31;
      Ilc              at 1*Word range 0  .. 1;  -- second word
      Cc               at 1*Word range 2  .. 3;
      Program_Mask     at 1*Word range 4  .. 7;
      Inst_Address     at 1*Word range 8  .. 31;
  end record;
30
for Program_Status_Word'Size use 8*System.Storage_Unit;
for Program_Status_Word'Alignment use 8;
NOTES
31
15  Note on the example: The record_representation_clause defines the record layout. The Size clause guarantees that (at least) eight storage elements are used for objects of the type. The Alignment clause guarantees that aliased, imported, or exported objects of the type will have addresses divisible by eight.

Notice that Ada allows not only the number of bits for each field, it also allows the programmer to specify the value range for each field, the bit location relative to the start of each word of the structure, and even leave some unused bits. C++ requires the use of unnamed bit-fields (with a size greater than 0) to mark unused bits. The C++ compiler has no way to know which bits the programmer wants to skip without specifying an unnamed field. Strangely, an unnamed field with a size of 0 indicates that the next field should start at an allocation unit's boundary, which is highly dependent on the hardware architecture. For instance, on an 8-bit processor the allocation boundary will be every 8 bits, while on a 32-bit processor the allocation boundary might be every 8 bits, 16 bits, or 32 bits, depending upon the processor architecture.

As a software safety engineer I find the implementation-defined aspects of C++ bit-fields to be very unsettling. A project may verify that its C++ bit-fields work as intended on a particular architecture, but when a technology refresh is performed in several years, there is no assurance that the C++ program will continue to work properly on the new hardware. The failure of the C++ program to run on the new hardware will be received as a complete surprise by most users of the upgraded system and by their management. The surprise may be accompanied by hazardous events because of incorrect control of a safety critical system. People may be injured or die, the environment may be seriously damaged, and very expensive systems may be damaged or destroyed. The root cause of the hazards will not be poor functional requirements, poor design, or programming mistakes. The root cause of the hazards will be exposure of implementation-defined behaviors in a programming language. Who will the lawyers sue over the consequences of those hazards?
  • 22 March 2014 at 00:45

tag:blogger.com,1999:blog-24476727792496689.post-1127314847775776658

Graph Representation Using an Adjacency List
Summary
The graph abstract data type is very useful for identifying relationships between entities. Graphs can be used to represent navigation on a map, with the entities being the way-points and the connections between the way-points being the named routes, with additional attributes of distance and travel time. Graphs can also be used to represent states and state transitions for finite state diagrams.

Both maps and finite state diagrams benefit from a directed graph, where relationships are directional. There are also undirected graphs, which can be used to model such things as molecular structures, where the atoms are the entities and the connections between them are the chemical bonds.

The following example shows an implementation of an undirected graph with a fixed number of entities. A fixed length graph is an efficient representation when the number of entities you are modeling will not grow, such as when you are modeling the structure of a protein. This example defines a generic data type for a graph. The generic data type allows the programmer to specialize the graph upon instantiation by specifying the type of the entity and the number of entities in the graph.

As is my custom, I am using the Ada programming language for my example. The generic abstract data type is defined in an Ada package, and a simple procedure is written to illustrate how the package is used.

Ada Package Specification
The Ada package specification defines the public interfaces to the abstract data type and the private internal data representations used in the abstract data type.
------------------------------------------------------------------
-- Generic Undirected Graph Package --
------------------------------------------------------------------
generic
type Element is private;
Max_Elements : Positive;
package Fixed_Length_Graph is
subtype Element_Id is Positive range 1..Max_Elements;
type Adjacency_List is array(Element_Id) of Boolean;
type Undirected_Graph is tagged private;
Procedure Add_Element(to : in out Undirected_Graph;
Item : in Element;
Id : in Element_Id);
Procedure Add_Adjacencies(to : in out Undirected_Graph;
Item : in Element_Id;
List : in Adjacency_List);
function Get_Element(From : in Undirected_Graph;
Id : in Element_Id) return Element;
function List_Adjacencies(From : in Undirected_Graph;
Id : in Element_Id) return Adjacency_List;
No_Element_Error : Exception;
private
type Adjacency_Matrix is array(Element_Id) of Adjacency_List;
type Element_Record is record
Filled : Boolean := False;
The_Element : Element;
end record;
type Element_List is array(Element_Id) of Element_Record;
type Undirected_Graph is tagged record
Adjacencies : Adjacency_Matrix := (Others => (Others => False));
Elements : Element_List;
end record;
end Fixed_Length_Graph;

This package provides two public data types: Adjacency_List, which is fully exposed to the public, and Undirected_Graph, which is declared to be private and is therefore an opaque type. The completion of the type Undirected_Graph is seen in the privatesection of the package specification, along with the private types Adjacency_Matrix, Element_Record, and Element_List, which are completely private, having no exposure to the public.
You may notice that Adjacency_List is an array of boolean values and Adjacency_Matrix is an array of Adjacency_List elements.
Ada Package Body
The Ada package body contains the implementation of all the functions and procedures declared in the Ada package specification. The contents of the Ada package body are completely hidden from any the calling subprograms. One consequence of this is that the calling subprogram never needs to be recompiled if only the package body is modified. The calling subprogram depends upon the interface specified in the package specification, not upon the implementation of that interface.
package body Fixed_Length_Graph is



-----------------
-- Add_Element --
-----------------



procedure Add_Element(to : in out Undirected_Graph;
                    Item : in Element;
                      Id : in Element_Id)is
begin
   To.Elements(Id).The_Element := Item;
   To.Elements(Id).Filled := True;
end Add_Element;



---------------------
-- Add_Adjacencies --
---------------------



procedure Add_Adjacencies(to : in out Undirected_Graph;
                        Item : in Element_Id;
                        List : in Adjacency_List) is
begin
   for I in List'Range loop
      To.Adjacencies(Item)(I) := List(I);
      To.Adjacencies(I)(Item) := List(I);
   end loop;
end Add_Adjacencies;



-----------------
-- Get_Element --
-----------------



function Get_Element(From : in Undirected_Graph;
                       Id : in Element_Id) return Element is
begin
   if From.Elements(Id).Filled then
      return From.Elements(ID).The_Element;
   else
      raise No_Element_Error;
   end if;
end Get_Element;



----------------------
-- List_Adjacencies --
----------------------



function List_Adjacencies(From : in Undirected_Graph;
                            Id : in Element_Id) return Adjacency_List is
begin
   return From.Adjacencies(Id);
end List_Adjacencies;



end Fixed_Length_Graph;
Remember that the Adjacency_Matrix is defined as an array of arrays, not as a two-dimensional array. This was done to simplify the ability to return the adjacencies associated with a specific entity or node.

Testing the Undirected Graph Package
In this test I have decided to create a graph of bounded string values. The graph I am representing is shown in the diagram below.



Test Source Code
------------------------------------------------------------------
-- Second test of Fixed_Length_Graph package --
------------------------------------------------------------------
with Ada.Text_Io; use Ada.Text_IO;
with Ada.Strings.Bounded;
with Fixed_Length_Graph;

procedure Graph_Test_02 is
package B_Strings is new Ada.Strings.Bounded.Generic_Bounded_Length(25);
use B_Strings;
package Str_Graph is new Fixed_Length_Graph(B_Strings.Bounded_String, 7);
use Str_Graph;
The_Graph: Undirected_Graph;
Adj_List : Adjacency_List;

Subtype Index is Positive range 1..7;
type Str_Array is array(Index) of B_Strings.Bounded_String;
Names : Str_Array;


Adj_Array : Array(Index) of Adjacency_List;

begin
Names(1) := To_Bounded_String("One");
Names(2) := To_Bounded_String("Two");
Names(3) := To_bounded_String("Three");
Names(4) := To_Bounded_String("Four");
Names(5) := To_Bounded_String("Five");
Names(6) := To_Bounded_String("Six");
Names(7) := To_Bounded_String("Seven");
for I in Names'Range loop
The_Graph.Add_Element(Names(I), I);
end loop;
Adj_Array(1) := (2 => True, 3 => True, Others => False);
Adj_Array(2) := (1 => True, 3 => True, 4 => True, Others => False);
Adj_Array(3) := (1 => True, 2 => True, Others => False);
Adj_Array(4) := (2 => True, 5 => True, 6 => True, Others => False);
Adj_Array(5) := (4 => True, 7 => True, Others => False);
Adj_Array(6) := (4 => True, 7 => True, Others => False);
Adj_Array(7) := (5 => True, 6 => True, Others => False);

for I in Adj_Array'Range loop
The_Graph.Add_Adjacencies(I, Adj_Array(I));
end loop;

for I in Element_Id'Range loop
Put(To_String(The_Graph.Get_Element(I)) & "-> ");
Adj_List := The_Graph.List_Adjacencies(I);
for J in Adj_List'Range loop
if Adj_List(J) = True then
Put(To_String(The_Graph.Get_Element(J)) & " ");
end if;
end loop;
New_Line;
end loop;

end Graph_Test_02;

The output of this program is:



One-> Two Three
Two-> One Three Four
Three-> One Two
Four-> Two Five Six
Five-> Four Seven
Six-> Four Seven

Seven-> Five Six
  • 19 November 2014 at 21:52

Parallel Addition Revisited

In an earlier article I discussed aspects of parallel addition of elements of an array. A more general problem would be to sum a set of numbers from a collection of numbers. The collection cannot be simply an arbitrary unending stream of numbers, because a sum of such a stream could never be completed. Instead, it must be some discrete sample size of numbers. Furthermore, the sample size cannot be zero. There must be at least one number in the sample or no sum is possible.

The following example collects a sample of numbers into a queue and then uses concurrent tasks to perform pair-wise addition of the queue elements. Each result of pair-wise addition is placed back on the queue. Eventually, given a discrete number of items in the queue, the queue will be left containing only one value. That value will be the total of all the operations on the set of numbers. This approach works because addition is commutative. The order the numbers are added is irrelevant to the solution.

Parallel Addition In Ada

The following source code accomplishes this more general parallel addition capability.
The key to the operation of the program is the protected object Queue, which contains the list of numbers shared by all the parallel tasks.
Each task performing addition calls the entry Get_Two, which dequeues two integers from the Queue object. The task then adds the two integers and calls the procedure Enqueue to place the result on the Queue object.

Parallel_Addition.ads
------------------------------------------------------------------

-- Parallel Addition Package                                    --

------------------------------------------------------------------

with Ada.Containers.Doubly_Linked_Lists;


package Parallel_Addition is

   Package Int_Queue is new Ada.Containers.Doubly_Linked_Lists(Integer);

   use Int_Queue;


   protected Queue is

      Entry Get_Two(Item1, Item2 : out Integer);

      Entry Dequeue(Item : out Integer);

      Procedure Enqueue(Item : in Integer);

      Procedure Clear;

      Function Count return Integer;

   private

      Nums : Int_Queue.List;

   end Queue;


   task type Adder;


end Parallel_Addition;

Inner workings of the Parallel_Addition package

I have chosen the Doubly Linked List package provided by the Ada container library to implement my queue. The Enqueue procedure appends values to the end of the list (queue) and both the Get_Two and Dequeue entries dequeue values from the beginning of the list (queue).


Ada entries have a boundary condition. The boundary condition for the Get_Two entry evaluates to TRUE when the list contains more than 1 element. The boundary condition for the Dequeue entry evaluates to TRUE when the list contains more than 0 elements. The Clear procedure empties the list. The Count function reports the number of tasks suspended on the Get_Two entry queue. Every task calling an entry will suspend until the boundary condition evaluates to TRUE. The tasks will then be allowed to execute the entry in FIFO order, ensuring that all tasks are allowed to reliably access the entry.


The Adder task type contains an infinite loop which simply calls Get_Two to dequeue two values from the Queue object, then Enqueues the sum of those two values onto the Queue. Since every pair of values is replaced with a single value, the size of the Queue rapidly gets smaller. Once there is only one value left in the Queue, the Adder tasks will all suspend waiting for a second value. The last value left on the Queue is the total of all the numbers placed on the Queue.


Parallel_Addition.adb
with Ada.Containers; use Ada.Containers;

package body Parallel_Addition is

   -----------

   -- Queue --

   -----------

   --------------------------------------------------------------------

   -- The Queue protected object treats its internal list as a queue.--

   -- All data is read from the head of the list and new data is     --

   -- appended to the end of the list. Reading data from the head    --

   -- also deletes that data from the queue.                         --

   --                                                                --

   -- The Count function reports the number of tasks waiting in the  --

   -- entry queue for the Get_Two entry.                             --

   -- The Clear procedure empties the internal list of its contents. --

   --------------------------------------------------------------------

   protected body Queue is

   -------------

   -- Get_Two --

   -------------

   entry Get_Two (Item1, Item2 : out Integer) when Nums.Length > 1 is

   begin

      Item1 := Nums.First_Element;

      Nums.Delete_First;

      Item2 := Nums.First_Element;

      Nums.Delete_First;

   end Get_Two;


   -------------

   -- Dequeue --

   -------------

   entry Dequeue (Item : out Integer) when Nums.Length > 0 is

   begin

      Item := Nums.First_Element;

      Nums.Delete_First;

   end Dequeue;


   -------------

   -- Enqueue --

   -------------

   procedure Enqueue (Item : in Integer) is

   begin

      Nums.Append(Item);

   end Enqueue;


   -----------

   -- Count --

   -----------


   function Count return Integer is

   begin

      Return Get_Two'Count;

   end Count;


   -----------

   -- Clear --

   -----------

   procedure Clear is

   begin

      Nums.Clear;

   end Clear;

   end Queue;


   -----------

   -- Adder --

   -----------

   ---------------------------------------------------------------------

   -- Each Adder task simply calls Get_Two to dequeue two values from --

   -- the queue. It then enqueues the sum of those two values back    --

   -- on the queue. The queue will contain the final sum when it      --

   -- contains only a single value and no tasks are holding the lock  --

   -- for the Get_Two entry.                                          --

   ---------------------------------------------------------------------

   task body Adder is

      Value1, Value2 : Integer;

   begin

      Loop

         Queue.Get_Two(Value1, Value2);

         Queue.Enqueue(Value1 + Value2);

      end loop;

   end Adder;

end Parallel_Addition;

A test driver for the Parallel_Addition package

The Parallel_Addition_Main procedure is my test driver for the Parallel_Addition package.

The subtype The_Nums is created to define a range of values to add. In the source code below the range of values is specified as -600 through 601 inclusive. The sum of this set of numbers should be 601.

An array of Adder tasks is created. The tasks start running immediately. Since there are no values in the Queue protected object, all the tasks will suspend in the Get_Two entry queue waiting for the boundary condition to evaluate to TRUE.

The test driver performs the addition 20 times. This is done to reveal any race conditions that might exist in the code. For each iteration of the test the Queue is cleared and the numbers are Enqueued into the Queue object. The test driver then polls the Queue.Count function to determine when all the Adder tasks are once again suspended in the Get_Two entry queue. That event indicates there is only one value left in the Queue, which is our total. That final value is Dequeued and displayed.
After the 20 iterations all the Adder tasks are aborted and the program ends in an orderly manner.

Parallel_Addition_Main.adb
------------------------------------------------------------------

-- Parallel Addition Main Program                               --

--                                                              --

-- This program uses a task pool contained in the array named   --

-- workers. The task pool is reused for multiple iterations.    --

-- The tasks are aborted at the end of the program.             --

------------------------------------------------------------------

with Parallel_Addition; use Parallel_Addition;

with Ada.Text_IO; use Ada.Text_IO;

with Ada.Task_Identification; use Ada.Task_Identification;


procedure Parallel_Addition_Main is

   subtype The_Nums is Integer range -600..601;

   Workers : array(1..3) of Adder;

   Total : Integer;

begin

   for I in 1..20 loop

      Put(I'Img & " ");

      Queue.Clear;

      for I in The_Nums'Range loop

         Queue.Enqueue(I);

      end loop;


      Loop

         exit when Queue.Count = Workers'Length;

      end loop;


      Queue.Dequeue(Total);

      Put_Line("The total of the series of numbers from " &

                The_Nums'First'Img & " to " &

                The_Nums'Last'Img & " is " &

                Total'Img);

   end loop;


   for Worker of Workers loop

      Abort_Task(Worker'Identity);

   end loop;

end Parallel_Addition_Main;


Program output
 1 The total of the series of numbers from -600 to  601 is  601

 2 The total of the series of numbers from -600 to  601 is  601

 3 The total of the series of numbers from -600 to  601 is  601

 4 The total of the series of numbers from -600 to  601 is  601

 5 The total of the series of numbers from -600 to  601 is  601

 6 The total of the series of numbers from -600 to  601 is  601

 7 The total of the series of numbers from -600 to  601 is  601

 8 The total of the series of numbers from -600 to  601 is  601

 9 The total of the series of numbers from -600 to  601 is  601

 10 The total of the series of numbers from -600 to  601 is  601

 11 The total of the series of numbers from -600 to  601 is  601

 12 The total of the series of numbers from -600 to  601 is  601

 13 The total of the series of numbers from -600 to  601 is  601

 14 The total of the series of numbers from -600 to  601 is  601

 15 The total of the series of numbers from -600 to  601 is  601

 16 The total of the series of numbers from -600 to  601 is  601

 17 The total of the series of numbers from -600 to  601 is  601

 18 The total of the series of numbers from -600 to  601 is  601

 19 The total of the series of numbers from -600 to  601 is  601

 20 The total of the series of numbers from -600 to  601 is  601


  • 6 April 2015 at 22:06

Ada 2012 Aspect Clauses => Static Predicates

John English wrote a wonderful book titled Ada 95: The Craft of Object-Oriented Programming, which is available on line at http://archive.adaic.com/docs/craft/html/contents.htm. In chapter 3 of that book he explores many Ada programming fundamentals using a simple calculator program example. The code for that example is:

    with Ada.Text_IO, Ada.Integer_Text_IO;

    use  Ada.Text_IO, Ada.Integer_Text_IO;

    procedure Calculator is

        Result   : Integer;

        Operator : Character;

        Operand  : Integer;

    begin

        Put ("Enter an expression: ");

        Get (Result);                                   --  1

        loop                                            --  2

            loop                                        --  3

                Get (Operator);

                exit when Operator /= ' ';

            end loop;

            if Operator = '.' then                      --  4

                Put (Result, Width => 1);               --  5

                exit;                                   --  6

            else

                Get (Operand);                          --  7

                case Operator is

                    when '+' =>

                        Result := Result + Operand;     --  8

                    when '-' =>

                        Result := Result - Operand;

                    when '*' =>

                        Result := Result * Operand;     --  9

                    when '/' =>

                        Result := Result / Operand;

                    when others =>

                        Put ("Invalid operator '");     -- 10

                        Put (Operator);

                        Put ("'");

                        exit;                           -- 11

                end case;

            end if;

        end loop;

        New_Line;

    end Calculator;

This program implements a simple left-to-right calculator that does not understand parentheses. Each calculation string is terminated with a period (full stop for those speaking the King’s English).

1 + 2 * 3.

The calculation string above is evaluated left to right, yielding the value 9. The calculator does not impose any operator precedence.

The internal loop beginning with the line labeled 3 in an end-of-line comment deals with the fact that the Get procedure for type Character simply gets the next character in the input stream. It does not skip space characters to input the next non-space character. The loop implemented by John English simply reads characters until it encounters something that is not a space character. The hope implicit in this program is that the person creating the input strings only uses integral numbers, spaces, or one of the characters ‘+’, ‘-‘, ‘*’, ‘-‘, or ‘.’. If it encounters any other character it declares that an invalid operator has been input and terminates the program.

As a software safety engineer I am always uncomfortable with a program that cannot clearly and concisely specify simple input values. What happens, for instance, in the John English version of this program if the person running the program inputs horizontal tabs instead of spaces? They may look similar on an editor screen, but they are distinctly different to program.

I know that John English was well aware of these issues, but was trying to introduce students to the fundamentals of Ada programming rather than the complications of error handling. In fact, until the 2012 version of Ada there was no simple way to do better.

Ada 2012 introduced aspect clauses. The form of the aspect clause of interest to this article is the static predicate. A static predicate is a qualification of a type or subtype definition defining a set of properties about the type. In Ada 95 the best you could do was to specify the range of values for a subtype or type. That range had to be contiguous, with no gaps in the values. The Ada 2012 static predicate definition introduces more flexibility. It allows any expression that can be evaluated as a membership test.

How does this relate to the calculator program shown above? The operators in that program are five distinct characters, but not a contiguous range of characters. Let’s see how we could express that set of characters using an Ada 2012 static predicate.


Subtype Acceptable is Character

   with Static_Predicate => Acceptable in ‘+’|’-‘|’*’|’/’|’.’;


The result is a subtype of Character with five valid values.

Once we have established a subtype with only the few values we want to use, we have managed to more clearly express the intent of our program. A comparable program to the Calculator program above, but using this static predicate expression is:
------------------------------------------------------------------

-- Simple Calculator Program                                    --

------------------------------------------------------------------

with Ada.Text_IO; use Ada.Text_IO;

with Ada.Integer_Text_Io; use Ada.Integer_Text_IO;


procedure Simple_Calculator is

   subtype Acceptable is Character

   with Static_Predicate => Acceptable in '+'|'-'|'*'|'/'|'.';


   function Get_Operator return Acceptable is

      Temp : Character;

   begin

      loop

         get(Temp);

         exit when Temp in Acceptable;

      end loop;

      return Temp;

   end Get_Operator;


   Operator : Acceptable;

   Operand  : Integer;

   Result   : Integer;

begin

   Get(Item => Result);

   Outer:

   loop

      Operator := Get_Operator;


      if Operator = '.' then

         Put(Item => Result, Width => 1);

         New_Line;

         exit Outer when End_Of_File;

         Get(Item => Result);

      else

         Get(Item => Operand);

         case Operator is

         when '+' => Result := Result + Operand;

         when '-' => Result := Result - Operand;

         when '*' => Result := Result * Operand;

         when '/' => Result := Result / Operand;

         when '.' => null;

         end case;

      end if;

   end loop Outer;

end Simple_Calculator;

I have encapsulated the operator input loop into a function to clearly specify my intentions. Note that the function Get_Operator now loops until it reads a character that is a member of the subtype Acceptable. This logic will read past spaces, tabs, or any other characters that are not members of the subtype Acceptable. The case statement now does not need a “when Others” clause because it explicitly handles all possible values of subtype Acceptable. If another operator were to be added to the set of values for Acceptable the compiler would flag the need to handle the new value in the case statement.
The use of the Static Predicate to create a discontinuous set of valid values can be very useful. It also greatly simplifies programming over creating a more complex set of conditional statements on an input loop. Finally, it allows a more robust use of the case clause.
  • 10 April 2015 at 23:34

Shared Resource Design Patterns

Summary

Many applications are constructed of groups of cooperating threads of execution. Historically this has frequently been accomplished by creating a group of cooperating processes. Those processes would cooperate by sharing data. At first, only files were used to share data. File sharing presents some interesting problems. If one process is writing to the file while another process reads from the file you will frequently encounter data corruption because the reading process may attempt to read data before the writing process has completely written the information. The solution used for this was to create file locks, so that only one process at a time could open the file. Unix introduced the concept of a Pipe, which is effectively a queue of data. One process can write to a pipe while another reads from the pipe. The operating system treats data in a pipe as a series of bytes. It does not let the reading process access a particular byte of data until the writing process has completed its operation on the data.
Various operating systems also introduced other mechanisms allowing processes to share data. Examples include message queues, sockets, and shared memory. There were also special features to help programmers control access to data, such as semaphores. When operating systems introduced the ability for a single process to operate multiple threads of execution, also known as lightweight threads, or just threads, they also had to provide corresponding locking mechanisms for shared data.
Experience shows that, while the variety of possible designs for shared data is quite large, there are a few very common design patterns that frequently emerge. Specifically, there are a few variations on a lock or semaphore, as well as a few variations on data buffering. This paper explores the locking and buffering design patterns for threads in the context of a monitor. Although monitors can be implemented in many languages, all examples in this paper are presented using Ada protected types. Ada protected types are a very thorough implementation of a monitor.

Monitors

There are several theoretical approaches to creating and controlling shared memory. One of the most flexible and robust is the monitor as first described by C.A.R. Hoare. A monitor is a data object with three different kinds of operations.

Procedures are used to change the state or values contained by the monitor. When a thread calls a monitor procedure that thread must have exclusive access to the monitor to prevent other threads from encountering corrupted or partially written data.

Entries, like procedures, are used to change the state or values contained by the monitor, but an entry also specifies a boundary condition. The entry may only be executed when the boundary condition is true. Threads that call an entry when the boundary condition is false are placed in a queue until the boundary condition becomes true. Entries are used, for example, to allow a thread to read from a shared buffer. The reading thread is not allowed to read the data until the buffer actually contains some data. The boundary condition would be that the buffer must not be empty. Entries, like procedures, must have exclusive access to the monitor's data.

Functions are used to report the state of a monitor. Since functions only report state, and do not change state, they do not need exclusive access to the monitor's data. Many threads may simultaneously access the same monitor through functions without danger of data corruption.

The concept of a monitor is extremely powerful. It can also be extremely efficient. Monitors provide all the capabilities needed to design efficient and robust shared data structures for threaded systems.
Although monitors are powerful, they do have some limitations. The operations performed on a monitor should be very fast, with no chance of making a thread block. If those operations should block, the monitor will become a road block instead of a communication tool. All the threads awaiting access to the monitor will be blocked as long as the monitor operation is blocked. For this reason, some people choose not to use monitors. There are design patterns for monitors that can actually be used to work around these problems. Those design patterns are grouped together as locking patterns.

Locking Patterns

This paper explores three locking patterns, binary semaphores, counting semaphores, and burst locks.

Binary Semaphore

Binary semaphores are used to provide exclusive locking for a thread on a resource. Only one thread at a time may "hold" the semaphore. All others must wait until their turn with the semaphore. If you want to lock access to a potentially blocking resource, you should acquire a semaphore for that resource just before accessing it. and release the semaphore upon completing your access to the resource. The following code block defines the interface to a monitor implementing a binary semaphore.
   protected type Binary_Semaphore is
      entry Acquire;
      procedure Release;
   private
      Locked : Boolean := False;
   end Binary_Semaphore;
This monitor has two operations defined. The entry Acquire allows a thread (or task as they are called in Ada) to acquire the semaphore if no other task currently holds the semaphore. The procedure Release unconditionally releases the semaphore. The private data for this monitor is a singleboolean value, Locked, which is initialized to False .
The actual workings of the monitor are specified in the body of the protected type.
   protected body Binary_Semaphore is
      entry Acquire when not Locked is
      begin
         Locked := True;
      end Acquire;

 

      procedure Release is
      begin
         Locked := False;
      end Release;
   end Binary_Semaphore;
Note that you can only acquire the binary semaphore when it is not already locked. Acquiring the semaphore causes it to be locked. The Release procedure simply unlocks the semaphore.

Counting Semaphore

Sometimes you want to access a resource with the ability to handle a limited number of simultaneous accesses. In this case, you will want to use a counting semaphore. This variation on the semaphore allows up to a predetermined maximum number of threads or tasks to hold the semaphore simultaneously. The interface for a counting semaphore looks a lot like the interface for a binary semaphore.
   protected type Counting_Semaphore(Max : Positive) is
      entry Acquire;
      procedure Release;
   private
      Count : Natural := 0;
   end Counting_Semaphore;
In this case, when the semaphore is created you must specify the maximum number of tasks you want to simultaneously hold this semaphore. The private data for this semaphore is a simple integer value, counting the current number of tasks currently holding the semaphore. In the case of a counting semaphore, the boundary condition for the Acquire entry is that the Count cannot exceed the value of Max. The implementation of the counting semaphore simply manipulates the Count.
   protected body Counting_Semaphore is
      entry Acquire when Count < Max is
      begin
         Count := Count + 1;
      end Acquire;

 

      procedure Release is
      begin
         if Count > 0 then
            Count := Count - 1;
         end if;
      end Release;
   end Counting_Semaphore;
Note that the Release procedure uses a conditional to ensure that Count is never assigned a negative value.

Burst Lock

The next locking pattern we will explore is the burst lock. This pattern allows some specified number of tasks simultaneous access to a resource. Access to the resource is granted based upon the number of tasks waiting to access the resource. This pattern is good for short duration resource accesses when there is a limited bandwidth for communication to the resource. You can use this pattern to optimize the number of simultaneous accesses to the resource.
   protected type Burst(Sample_Size : Positive) is
      entry Request_Access;
      procedure Grant_Access;
   private
      Release : Boolean := False;
   end Burst;
You specify the number of tasks in a burst when you create an instance of this type. Note that there is an entry Request_Access and a procedure Grant_Access. The entry queues up requests until Sample_Size tasks are waiting to access the resource. The procedure Grant_Access is used to send a burst through when fewer than Sample_Size tasks are waiting.
   protected body Burst is
      entry Request_Access when Request_Access'Count = Sample_Size or Release is
      begin
         if Request_Access'Count > 0 then
            Release := True;
         else
            Release := False;
         end if;
      end Request_Access;

 

      procedure Grant_Access is
      begin
         Release := True;
      end Grant_Access;
   end Burst;
The Request_Access entry does all the interesting work in this pattern. The expression Request_Access'Count evaluates to the number of tasks queued up to wait for this entry. The boundary condition is true when the number of waiting tasks equals Sample_Size or Release is true. When the boundary condition becomes true, the entry sets Release to True until no more tasks are queued. At that point Release is set to False so that access to the resource will once again be blocked.

Squad Locks

A squad lock allows a special task (the squad leader) to monitor the progress of a herd or group of worker tasks. When all (or a sufficient number) of the worker tasks are done with some aspect of their work, and the leader is ready to proceed, the entire set of tasks is allowed to pass a barrier and continue with the next sequence of their activities. The purpose is to allow tasks to execute asynchronously, yet coordinate their progress through a complex set of activities.
package Barriers is
   protected type Barrier(Trigger : Positive) is
      entry Wait_For_Leader; 
      entry Wait_For_Herd; 
      procedure Leader_Done; 
   private
      Done : Boolean := False;
   end Barrier;

 

   protected type Autobarrier(Trigger : Positive) is
      entry Wait_For_Leader; 
      entry Wait_For_Herd; 
   private
      Done : Boolean := False;
   end Autobarrier;
end Barriers;
This package shows two kinds of squad lock. The Barrier protected type demonstrates a basic squad lock. The herd calls Wait_For_Leader and the leader calls Wait_For_Herd and then Leader_Done. The Autobarrier demonstrates a simpler interface. The herd calls Wait_For_Leader and the leader calls Wait_For_Herd. The Trigger parameter is used when creating an instance of either type of barrier. It sets the minimum number of herd tasks the leader must wait for before it can proceed.
package body Barriers is
   protected body Barrier is
      entry Wait_For_Herd when Wait_For_Leader'Count >= Trigger is
      begin
         null;
      end Wait_For_Herd;

 

      entry Wait_For_Leader when Done is
      begin
         if Wait_For_Leader'Count = 0 then
            Done := False;
         end if;
      end Wait_For_Leader;

 

      procedure Leader_Done is
      begin
         Done := True;
      end Leader_Done;
   end Barrier;

 

   protected body Autobarrier is
      entry Wait_For_Herd when Wait_For_Leader'Count >= Trigger is
      begin
         Done := True;
      end Wait_For_Herd;

 

      entry Wait_For_Leader when Done is
      begin
         if Wait_For_Leader'Count = 0 then
            Done := False;
         end if;
      end Wait_For_Leader;
   end Autobarrier;
end Barriers;

Buffer Patterns

Buffer patterns are used when you want the monitor to control data shared by two or more tasks. The kind of buffer you use will vary with your needs. If all the data produced by one thread must be read by the another thread, then you want to use a buffer containing one or more data elements. Buffers containing only one data element cause the reading and writing tasks to become synchronized around the read and write operations. Buffers containing many data elements allow greater asynchronous operation of the reading and writing tasks. Multi-element buffers can either be bounded, with a fixed maximum size, or unbounded, being dynamically allocated and de-allocated as needed. Bounded buffers offer fixed amount of memory usage. Unbounded buffers offer less blocking due to the buffer being full.
Additional patterns are available when the reading task only needs a time based sample of the data from the writing task, or when the reading task only needs to be sure it is not reading duplicate data points.

Bounded Buffer

The bounded buffer holds a limited collection of data items. Any task reading from the bounded buffer can only read data when the buffer contains data. Any task writing to the bounded buffer can only write data when the buffer is not full. You could make a variation of the bounded buffer having the write operation be a procedure. When the buffer is full it will overwrite the oldest data item in the buffer. The example below demonstrates the former version, where the writing task suspends when the buffer is full.
   type Element_Array is array(Natural range <>) of Element_Type;

 

   protected type Bounded_Buffer(Max : Positive) is
      entry Put(Item : in Element_Type);
      entry Get(Item : out Element_Type);
      function Size return Natural;
   private
      Elements  : Element_Array(0..Max);
      Put_Index : Natural := 0;
      Get_Index : Natural := 0;
      Count     : Natural := 0;
   end Bounded_Buffer;
The internal array Elements is treated as a circular queue of data. When the queue is full the Put entry blocks. When the queue is empty the Get entry blocks. The implementation of the bounded buffer is very simple.
   protected body Bounded_Buffer is
      entry Put(Item : in Element_Type) when Size < Elements'Length is
      begin
         Elements(Put_Index) := Item;
         Put_Index := (Put_Index + 1) mod Elements'Length;
         Count := Count + 1;
      end Put;

 

      entry Get(Item : out Element_Type) when Size > 0 is
      begin
         Item := Elements(Get_Index);
         Get_Index := (Get_Index + 1) mod Elements'Length;
         Count := Count - 1;
      end Get;

 

      function Size return Natural is
      begin
         return Count;
      end Size;
   end Bounded_Buffer;
The Put entry barrier is true as long as the Elements array is not full. The Get entry barrier is true as long as the Elements array is not empty. This design pattern conserves memory while ensuring that all data produced by the writing task will be available to the reading task.

Unbounded Buffer

The unbounded buffer uses varying amounts of memory, but also tends to minimize blocking for the writing task. One danger of this pattern is that you can run out of memory on your system if the writing task is consistently faster than the reading task for an extended period of time. For this reason, it is best to use unbounded buffers only when, on average, the writing task is no faster than the reading task.
   type Node;

 

   type Node_Access is access Node;

 

   type Node is record
      Value : Element_Type;
      Next  : Node_Access := null;
   end record;

 

   protected type Unbounded_Buffer is
      procedure Put(Item : in Element_Type);
      entry Get(Item : out Element_Type);
      function Size return Natural;
   private
      Head : Node_Access := null;
      Tail : Node_Access := null;
      Count : Natural := 0;
   end Unbounded_Buffer;
The unbounded buffer maintains a simple linked list of data elements. The Get entry is still limited by the boundary condition that the buffer cannot be empty when you get a data item.
   protected body Unbounded_Buffer is
      procedure Put(Item : in Element_Type) is
         Temp_Node : Node_Access := new Node;
      begin
         Temp_Node.Value := Item;
         if Tail = null then
            Head := Temp_Node;
            Tail := Temp_Node;
         else
            Tail.Next := Temp_Node;
            Tail := Tail.Next;
         end if;
         Count := Count + 1;
      end Put;

 

      entry Get(Item : out Element_Type) when Head /= null is
         procedure Free is new 
            Ada.Unchecked_Deallocation(Object => Node, Name => Node_Access);
         Temp : Node_Access;
      begin
         Item := Head.Value;
         Temp := Head;
         Head := Head.Next;
         if Head = null then
            Tail := null;
         end if;
         Free(Temp);
         Count := Count - 1;
      end Get;

 

      function Size return Natural is
      begin
         return Count;
      end Size;
   end Unbounded_Buffer;
This implementation adds to the tail of the linked list and reads (and deletes) from the head of the linked list. Every Put allocates a new node while every Get deallocates an existing node. These operations can be much slower than the corresponding bounded buffer operations due to the need to dynamically manage the data. In general, for long running applications, it is safer to use the bounded buffer than the unbounded buffer because you can run out of memory if the Put operations occur more often than the Get operations.

Single Element Buffers

There are a variety of single element buffer design patterns. I will deal with three of them.

Unconditional Buffer

A single element buffer without any access barrier is used when the reading task only needs to sample data from the writing task. If the reading task executes faster than the writing task, the reading task will read the same value more than once. If the writing task executes faster than the reading task some values will be skipped. Unconditional buffers are often used when sampling sensor data. Sensor data may be delivered to a program at a rate many times faster than it can be analyzed. The unconditional buffer simplifies the communication between the task reading from the sensor and the task analyzing the sensor data.
   protected type Read_Any_Buffer is
      procedure Put(Item : in Element_Type);
      function Get return Element_Type;
      function Initialized return Boolean;
   private
      Value    : Element_Type;
      Is_Valid : Boolean := False;
   end Read_Any_Buffer;
One issue with an unconditional buffer is determining if it contains valid data. It is unreasonable for the reading task to read uninitialized data. The function initialized can be polled to determine when the unconditional buffer has been initialized. After that happens the reading task merely calls the Get function whenever it wants access to the current value in the buffer.
   protected body Read_Any_Buffer is
      procedure Put(Item : in Element_Type) is
      begin
         Value    := Item;
         Is_Valid := True;
      end Put;

 

      function Get return Element_Type is
      begin
         if not Is_Valid then
            raise Uninitialized_Data;
         end if;
         return Value;
      end Get;

 

      function Initialized return Boolean is
      begin
         return Is_Valid;
      end Initialized;
   end Read_Any_Buffer;
This example has the Get function raise the exception Uninitialized_Data if the function is called before data is initialized. The exception logic was placed in this function for safety only. It is much more efficient to poll the Initialized function than to iteratively handle exceptions.

Conditional Read Buffer

Sometimes you want the reading task to only read data it has not seen before. The conditional read buffer handles this by allowing a conditional read. If the reading task is faster than the writing task this buffer will cause both tasks to synchronize around the write to the buffer.
   protected type Read_New_Buffer is
      procedure Put(Item : in Element_Type);
      entry Get(Item : out Element_Type);
   private
      Value  : Element_Type;
      Is_New : Boolean := False;
   end Read_New_Buffer;
Instead of an initialization flag I have provided an Is_New flag. Logically, once the reading task reads a data point that data becomes invalid. Another read cannot occur until the data is refreshed by the writing task.
   protected body Read_New_Buffer is
      procedure Put(Item : in Element_Type) is
      begin
         Value  := Item;
         Is_New := True;
      end Put;

 

      entry Get(Item : out Element_Type) when Is_New is
      begin
         Item   := Value;
         Is_New := False;
      end Get;
   end Read_New_Buffer;
The act of reading the buffer causes the data in the buffer to become invalid for another read. This pattern works for a system with a single reader task. If you have multiple reader tasks you must get more creative. One solution is to replace the Is_New boolean value with an array of booleanvalues, one for each reader. Another is to accompany the data value with a serial number. The reading task must keep track of the serial number of the last read data and reject the data if the serial number is unchanged.

Conditional Read Write Buffer

The conditional read write buffer is used when you want the reading task to read every value produced by the writing task. This buffer pattern causes the reading and writing tasks to always synchronize around the buffer reads and writes. If you really need this guarantee of data delivery you should carefully consider using the Unbounded Buffer pattern. The unbounded buffer allows a little more variation is speed between the reading and writing tasks. However, if one of the tasks is always faster than the other, the conditional read write buffer will perform faster than the unbounded buffer.
   protected type Read_Write_New_Buffer is
      entry Put(Item : in Element_Type);
      entry Get(Item : out Element_Type);
   private
      Value  : Element_Type;
      Is_New : Boolean := False;
   end Read_Write_New_Buffer;
Note that both the Put and the Get operations are conditional. Only one boolean value is needed to control both operations.
   protected body Read_Write_New_Buffer is
      entry Put(Item : in Element_Type) when not Is_New is
      begin
         Value  := Item;
         Is_New := True;
      end Put;

 

      entry Get(Item : out Element_Type) when Is_New is
      begin
         Item   := Value;
         Is_New := False;
      end Get;
   end Read_Write_New_Buffer;
The writing task can only write to the buffer when the data is not new. The reading task can only read from the buffer when the data is new. This pattern is faster than the bounded or unbounded buffer patterns because there is no collection manipulation. The bounded buffer requires calculations of put and get indices. The unbounded buffer requires memory allocation for every put and de-allocation for every get.
Whenever possible I suggest you use the single element buffers instead of the bounded or unbounded buffers. Most of the time one of your tasks will always be faster than the other in a read/write pair. In those cases you can avoid extra overhead, and maximize data throughput by using the single element buffers. The single element buffers also have the virtue of using less memory, which may be a concern in an embedded real time system.

Complete Ada Packages for Examples

Locks

package Locks is
   protected type Burst(Sample_Size : Positive) is
      entry Request_Access;
      procedure Grant_Access;
   private
      Release : Boolean := False;
   end Burst;

 

   protected type Counting_Semaphore(Max : Positive) is
      entry Acquire;
      procedure Release;
   private
      Count : Natural := 0;
   end Counting_Semaphore;

 

   protected type Binary_Semaphore is
      entry Acquire;
      procedure Release;
   private
      Locked : Boolean := False;
   end Binary_Semaphore;
end Locks;

 

package body Locks is
   protected body Burst is
      entry Request_Access when Request_Access'Count = Sample_Size or Release is
      begin
         if Request_Access'Count > 0 then
            Release := True;
         else
            Release := False;
         end if;
      end Request_Access;

 

      procedure Grant_Access is
      begin
         Release := True;
      end Grant_Access;
   end Burst;

 

   protected body Counting_Semaphore is
      entry Acquire when Count < Max is
      begin
         Count := Count + 1;
      end Acquire;

 

      procedure Release is
      begin
         if Count > 0 then
            Count := Count - 1;
         end if;
      end Release;
   end Counting_Semaphore;

 

   protected body Binary_Semaphore is
      entry Acquire when not Locked is
      begin
         Locked := True;
      end Acquire;

 

      procedure Release is
      begin
         Locked := False;
      end Release;
   end Binary_Semaphore;
end Locks;

Buffers

generic

 

   type Element_Type is private;

 

package Buffers is
   type Element_Array is array(Natural range <>) of Element_Type;

 

   protected type Bounded_Buffer(Max : Positive) is
      entry Put(Item : in Element_Type);
      entry Get(Item : out Element_Type);
      function Size return Natural;
   private
      Elements : Element_Array(0..Max);
      Put_Index : Natural := 0;
      Get_Index : Natural := 0;
      Count     : Natural := 0;
   end Bounded_Buffer;

 

   type Node;

 

   type Node_Access is access Node;

 

   type Node is record
      Value : Element_Type;
      Next  : Node_Access := null;
   end record;

 

   protected type Unbounded_Buffer is
      procedure Put(Item : in Element_Type);
      entry Get(Item : out Element_Type);
      function Size return Natural;
   private
      Head : Node_Access := null;
      Tail : Node_Access := null;
      Count : Natural := 0;
   end Unbounded_Buffer;

 

   Uninitialized_Data : exception;

 

   protected type Read_Any_Buffer is
      procedure Put(Item : in Element_Type);
      function Get return Element_Type;
      function Initialized return Boolean;
   private
      Value : Element_Type;
      Is_Valid : Boolean := False;
   end Read_Any_Buffer;

 

   protected type Read_New_Buffer is
      procedure Put(Item : in Element_Type);
      entry Get(Item : out Element_Type);
   private
      Value : Element_Type;
      Is_New : Boolean := False;
   end Read_New_Buffer;

 

   protected type Read_Write_New_Buffer is
      entry Put(Item : in Element_Type);
      entry Get(Item : out Element_Type);
   private
      Value : Element_Type;
      Is_New : Boolean := False;
   end Read_Write_New_Buffer;
end Buffers;

 

with Ada.Unchecked_Deallocation;

 

package body Buffers is
   protected body Bounded_Buffer is
      entry Put(Item : in Element_Type) when Size < Elements'Length is
      begin
         Elements(Put_Index) := Item;
         Put_Index := (Put_Index + 1) mod Elements'Length;
         Count := Count + 1;
      end Put;

 

      entry Get(Item : out Element_Type) when Size > 0 is
      begin
         Item := Elements(Get_Index);
         Get_Index := (Get_Index + 1) mod Elements'Length;
         Count := Count - 1;
      end Get;

 

      function Size return Natural is
      begin
         return Count;
      end Size;
   end Bounded_Buffer;

 

   protected body Unbounded_Buffer is
      procedure Put(Item : in Element_Type) is
         Temp_Node : Node_Access := new Node;
      begin
         Temp_Node.Value := Item;
         if Tail = null then
            Head := Temp_Node;
            Tail := Temp_Node;
         else
            Tail.Next := Temp_Node;
            Tail := Tail.Next;
         end if;
         Count := Count + 1;
      end Put;

 

      entry Get(Item : out Element_Type) when Head /= null is
         procedure Free is new Ada.Unchecked_Deallocation(Object => Node, Name => Node_Access);
         Temp : Node_Access;
      begin
         Item := Head.Value;
         Temp := Head;
         Head := Head.Next;
         if Head = null then
            Tail := null;
         end if;
         Free(Temp);
         Count := Count - 1;
      end Get;

 

      function Size return Natural is
      begin
         return Count;
      end Size;
   end Unbounded_Buffer;

 

   protected body Read_Any_Buffer is
      procedure Put(Item : in Element_Type) is
      begin
         Value := Item;
         Is_Valid := True;
      end Put;

 

      function Get return Element_Type is
      begin
         if not Is_Valid then
            raise Uninitialized_Data;
         end if;
         return Value;
      end Get;

 

      function Initialized return Boolean is
      begin
         return Is_Valid;
      end Initialized;
   end Read_Any_Buffer;

 

   protected body Read_New_Buffer is
      procedure Put(Item : in Element_Type) is
      begin
         Value := Item;
         Is_New := True;
      end Put;

 

      entry Get(Item : out Element_Type) when Is_New is
      begin
         Item := Value;
         Is_New := False;
      end Get;
   end Read_New_Buffer;

 

   protected body Read_Write_New_Buffer is
      entry Put(Item : in Element_Type) when not Is_New is
      begin
         Value := Item;
         Is_New := True;
      end Put;

 

      entry Get(Item : out Element_Type) when Is_New is
      begin
         Item := Value;
         Is_New := False;
      end Get;
   end Read_Write_New_Buffer;
end Buffers;

  • 25 May 2015 at 15:46

Ada 2012 Type Invariants and Predicates

Constrained Data Types and Subtypes

The Ada language has always had a limited ability to specify type invariants through the definition of range constraints on scalar data types. Along with giving the programmer the ability to specify range constraints, the Ada language has always provided some pre-defined subtypes of the Integer data type. For instance, the subtype Natural is defined as

subtype Natural is Integer range 0..Integer’Last;


This definition specifies that Natural is an integer with a constrained range of values, in this case the smallest valid Natural value is 0 and the highest is the maximum valid Integer value. Similarly, Ada provides the pre-defined subtype Positive, defined as

subtype Positive is Integer range 1..Integer’Last;


Type Integer is a signed data type, allowing both positive and negative values, but subtype Positive is an integer that can only contain a positive value.
While range specifications are very useful, they are also highly limited in their usefulness. Ada ranges must always be contiguous. This means that you cannot specify an integer data type of only even numbers, for instance. Even numbers are not contiguous.
More general type invariants are made available in the Ada 2012 language. There are two kinds of type invariants: static invariants and dynamic invariants. A static invariant is one that can be determined at compile time, such as a discrete list of valid values. Type invariants are contractual within a program.
Subtype predicates, on the other hand are more like value constraints, similar to a range constraint on a subtype. A dynamic predicate can be used, for instance, to define a subtype containing only even numbers.

subtype Even is Integer with Dynamic_Predicate => Even mod 2 = 0;


A  dynamic predicate allows the definition of a type that must be evaluated at run-time. In the case above every value in the subtype Even must satisfy the Dynamic_Predicate.

A Prime Number subtype

I decided to explore the possibility of creating a constrained subtype that only contains prime numbers. I wanted to understand how complex it would be to define such a type, and how inefficient such a subtype might be to use.


Table 1 Primes package specification
------------------------------------------------------------------

-- This package defines a subtype of integer                    --

-- All valid values in this subtype are prime numbers           --

------------------------------------------------------------------


package Primes is

   subtype Prime_Num is Integer with

   Dynamic_Predicate => Is_Prime(Prime_Num);

  

   -- Primality Test

   function Is_Prime(Item : Positive) return Boolean;

  

   -- Return the smallest prime number greater than the parameter

   function Next(Item : Positive) return Prime_Num

     with Pre => Item < Integer'Last;

  

   -- Return the greatest prime number less than the parameter

   function Previous(Item : Positive) return Prime_Num

     with Pre => Item > 2;

end Primes;

This was encouraging. Defining a prime number integer data type is very simple. The Dynamic_Predicate expression must evaluate to True or False, so I created a primality test returning a Boolean value. I added functions Next and Previous to allow iteration through the set of values in my Prime_Num type. Note that I called this a set of values and not a range of values. The set of prime numbers is very much discontinuous.
The package body for the Primes package reveals how simple the three functions actually are.

Table 2 Primes package body
with Ada.Numerics.Elementary_Functions; use Ada.Numerics.Elementary_Functions;


package body Primes is


   --------------

   -- Is_Prime --

   --------------


   function Is_Prime (Item : Positive) return Boolean is

      T : Positive := 2;

      Limit : Integer := Integer(Sqrt(Float(Item)));

   begin

      if Item = 2 then

         return True;

      end if;

      while T <= Limit loop

         if Item rem T = 0 then

            return False;

         end if;

         T := T + (if T = 2 then 1 else 2);

      end loop;

      return True;

   end Is_Prime;


   ----------

   -- Next --

   ----------


   function Next (Item : Positive) return Prime_Num is

      Value : Integer := Item + 1;

   begin

      while Value <= Integer'Last loop

         if Value in Prime_Num then

            return Value;

         end if;

         Value := Value + 1;

      end loop;

      return Item;

   end Next;


   --------------

   -- Previous --

   --------------


   function Previous (Item : Positive) return Prime_Num is

      Value : Integer := Item - 1;

   begin

      while Item >= 2 loop

         if Value in Prime_Num then

            return Value;

         end if;

         Value := Value - 1;

      end loop;

      return Item;

   end Previous;


end Primes;

The Is_Prime function is a slight modification of a naïve primality test. The function Next tests the values greater than the parameter passed to it and returns the first prime number it encounters. Notice that the test within the Next function is a simple type membership test. That membership test is based upon the Dynamic_Predicate specified for the subtype Prime_Num. In other words, it returns the result of the Is_Prime function. Similarly, the Previous function searches the integers less than the parameter for the first value that is in the Prime_Num subtype, and returns that value. If there is no prime number within the range of Integer that is greater than the parameter passed to Next, the function simply returns the parameter. A dynamic predicate is checked as a post-condition for every function returning a value of the type with that dynamic predicate. If the Next function returns a value that is not a prime number the function will raise an Assertion Error exception.
What then is the inefficiency associated with using a Dynamic_Predicate? As you can see, the function called within the Dynamic_Predicate is called twice in both the Next and Previous functions. One time within the body of the function and another time as a post-condition check of the function.
The following is a test program I created to measure the time required to execute the Next and Previous functions for the Prime_Num subtype.

Table 3 Primes test program
------------------------------------------------------------------

-- Primes Test                                                  --

------------------------------------------------------------------

with Ada.Text_Io; use Ada.Text_IO;

with Primes; use Primes;

with Ada.Calendar; use Ada.Calendar;


procedure Primes_Test is

   package Int_Io is new Ada.Text_IO.Integer_IO(Integer);

   use Int_IO;

   T_Start : Time;

   T_End   : Time;

   P       : Prime_Num;

begin

   Put_Line("Test the Next function to generate the next prime number");

   for Num in 1..20 loop

      Put(Item => Num, Width => 12);

      Put(" =>");

      T_Start := Clock;

      P := Next(Num);

      T_End := Clock;

      Put(Item => P, Width => 12);

      Put("   duration:" & Duration'Image(T_End - T_Start) &

         " seconds");

      New_Line;

   end loop;

   New_Line;

   Put_Line("Test the Previous function");

   for num in reverse Integer'Last - 20 .. Integer'Last loop

      Put(Item => Num, Width => 12);

      Put(" =>");

      T_Start := Clock;

      P := Previous(Num);

      T_End := Clock;

      Put(Item => P, Width => 12);

      Put("   duration:" & Duration'Image(T_End - T_Start) &

         " seconds");

      New_Line;

   end loop;

end Primes_Test;

As you can see, I instrumented the calls to Next and Previous so that I could capture the elapsed time spent in those calls.
For reference purposes, the results below were obtained using a system with an AMD A10-5700 APU operating at 3.40 GHz, using the Windows 10 operating system.

Test the Next function to generate the next prime number

           1 =>           2   duration: 0.000001414 seconds

           2 =>           3   duration: 0.000000283 seconds

           3 =>           5   duration: 0.000000566 seconds

           4 =>           5   duration: 0.000000283 seconds

           5 =>           7   duration: 0.000000283 seconds

           6 =>           7   duration: 0.000000283 seconds

           7 =>          11   duration: 0.000000566 seconds

           8 =>          11   duration: 0.000000566 seconds

           9 =>          11   duration: 0.000000283 seconds

          10 =>          11   duration: 0.000000282 seconds

          11 =>          13   duration: 0.000000283 seconds

          12 =>          13   duration: 0.000000283 seconds

          13 =>          17   duration: 0.000000282 seconds

          14 =>          17   duration: 0.000000566 seconds

          15 =>          17   duration: 0.000000283 seconds

          16 =>          17   duration: 0.000000566 seconds

          17 =>          19   duration: 0.000000282 seconds

          18 =>          19   duration: 0.000000283 seconds

          19 =>          23   duration: 0.000000849 seconds

          20 =>          23   duration: 0.000000566 seconds


Test the Previous function

  2147483647 =>  2147483629   duration: 0.000217217 seconds

  2147483646 =>  2147483629   duration: 0.000217217 seconds

  2147483645 =>  2147483629   duration: 0.000218913 seconds

  2147483644 =>  2147483629   duration: 0.000216934 seconds

  2147483643 =>  2147483629   duration: 0.000216652 seconds

  2147483642 =>  2147483629   duration: 0.000216652 seconds

  2147483641 =>  2147483629   duration: 0.000204772 seconds

  2147483640 =>  2147483629   duration: 0.000204489 seconds

  2147483639 =>  2147483629   duration: 0.000204490 seconds

  2147483638 =>  2147483629   duration: 0.000204772 seconds

  2147483637 =>  2147483629   duration: 0.000204206 seconds

  2147483636 =>  2147483629   duration: 0.000204207 seconds

  2147483635 =>  2147483629   duration: 0.000204206 seconds

  2147483634 =>  2147483629   duration: 0.000204490 seconds

  2147483633 =>  2147483629   duration: 0.000179317 seconds

  2147483632 =>  2147483629   duration: 0.000179317 seconds

  2147483631 =>  2147483629   duration: 0.000179317 seconds

  2147483630 =>  2147483629   duration: 0.000179034 seconds

  2147483629 =>  2147483587   duration: 0.000283683 seconds

  2147483628 =>  2147483587   duration: 0.000283965 seconds

  2147483627 =>  2147483587   duration: 0.000283683 seconds


As expected, it takes a lot longer to calculate the next or previous prime number for a very large number than it does for a small number.
  • 10 August 2015 at 00:06

Comparing Ada and High Integrity C++

I have often suspected that use of a safety critical or high integrity coding standard for C++ would yield a level of safety and software reliability approximately equivalent to using Ada with no restrictions.
I have documented a comparison of the High Integrity C++ Coding Standard (HIC) produced by PRQA with standard Ada language features. I was mostly correct in my suspicions. There are some rules in the HIC which apply equally well to Ada, such as a prohibition against the use of the goto statement.
In many instances the HIC rules require a non-trivial amount of code development and verification, while the Ada solution is trivial. For instance, achieving object initialization in C++ requires the use of carefully implemented constructors, while specifying default initialization for Ada records is relatively trivial. Another example is C++ multi-threading. The HIC lists several rules for the use of locks, mutexes, and condition variables. For Ada, the built-in facilities of task Rendezvous for direct task communication, and protected objects for communication through shared buffers, includes implicit control of locks, mutexes, and condition variables.

A PDF version of this document is available at https://drive.google.com/open?id=0B0y7MZFreWQtRy0wR0g3NTF6XzQ

My comparison follows:

_________________________________________________________________________

Comparison of Ada 2012 with the PRQA High Integrity C++ Coding Standard
This document compares the safety critical subset of C++ defined in the High Integrity C++ Coding Standard Version 4.0 (HIC) produced by PRQA and available at www.codingstandard.com.
The comparison will follow the flow of the HIC.

1.     General

1.1.  Implementation Compliance

1.1.1.     Ensure that code complies with the 2011 ISO C++ Language Standard

Compilers often provide features beyond those defined in the Standard, and unrestricted usage of such features will likely hamper code portability. To this end code should be routinely parsed with a separate compiler or code analysis tool apart from the compiler used for production purposes.

Corresponding Ada rule:

Compilers often provide features beyond those defined in the Standard, and unrestricted usage of such features will likely hamper code portability. To this end Ada compilers provide the Ada profile No_Implementation_Extensions, which enforces the restriction to Standard Ada in a standard way. The only thing not caught by this profile is the use of implementation-defined library packages, such as the GNAT packages for the GNAT compiler. Those packages can be caught by adding the No_Dependence profile.

1.2.  Redundancy

1.2.1.     Ensure that all statements are reachable

For the purposes of this rule, missing else and default clauses are considered also.
If a statement cannot be reached for any combination of function inputs (e.g. function arguments, global variables, volatile objects), it can be eliminated.

Corresponding Ada rule:

While Ada contains the same rule, Ada compilers perform reachability analysis and report compilation errors when code is not reachable.
Ada compilers identify missing others clauses in case statements when all possible values are not explicitly included in a case statement.

1.2.2.     Ensure that no expression or sub-expression is redundant

An expression statement with no side effects can be removed or replaced with a null statement without effecting behavior of the program.
Similarly, it is sometimes possible to simplify an expression by removing operands that do not change the resulting value of the expression, for example by multiplying by 1 or 0.
Redundant code causes unnecessary maintenance overhead and may be a symptom of a poor design.

Corresponding Ada rule:

The corresponding Ada rule would be identical. This rule applies to any programming language.

1.3.  Deprecated Features

1.3.1.     Do not use the increment operator (++) on a variable of type bool

Incrementing an object of type bool results in its value being set to true. This feature was deprecated in the 1998 C++ Language Standard and thus may be withdrawn in a later version.
Prefer to use an explicit assignment to true.

Corresponding Ada rule:

The Ada Boolean type is an enumerated type with the values of (False, True). Ada does not allow arithmetic operations on enumerated types. Furthermore, Ada does not provide an increment operator.

1.3.2.     Do not use the register keyword

Most compilers ignore the register keyword, and perform their own register assignments. Moreover, this feature was deprecated in the 2011 Language Standard and thus may be removed in a later version.

Corresponding Ada rule:

Ada does not provide any equivalent to the register keyword in C++.

1.3.3.     Do not use the C Standard Library .h headers

The C standard library headers are included in C++ for compatibility. However, their inclusion was deprecated in the 1998 C++ Language Standard and thus may be withdrawn in a later version.

Corresponding Ada rule:

Ada does not provide a preprocessor and does not interface with C or C++ programs at the source code level.

1.3.4.     Do not use deprecated STL features

The following STL features were deprecated in the 2011 C++ Language Standard and thus may be withdrawn in a later version:
·         Std::auto_ptr
·         Std::bind1st
·         Std::bind2nd
·         Std::ptr_mem_fun_ref
·         Std::unary_function
·         Std::binary_function
Of particular note is std::auto_ptr as it has been suggested that a search and replace of this type to std::unique_ptr may uncover latent bugs to the incorrect use of std::auto_ptr.

Corresponding Ada rule:

Avoid the use of the language features listed in Appendix J of the Ada 2012 Language Reference Manual. Use Ada 2012 aspect specifications instead. Avoiding Annex J features can be enforced through the use of the profile No_Obsolescent_Features.

1.3.5.     Do not use throw exception specifications

Specifying an exception specification using throw( type-id-listopt ) has been deprecated in the 2011 C++ Language Standard and thus may be removed in a future version.
The new syntax is to use noexcept or noexcept( expr).

Corresponding Ada rule:

Ada does not have an equivalent to either noexcept or noexcept(expr). In C++ the noexcept keyword verifies that no exception is thrown in a specified function. The noexcept(expr) declares that a function may throw exceptions for some types but not other types. Both forms of noexcept return a Boolean result.
The SPARK subset of Ada prohibits exceptions and is able to prove that a subprogram will not raise an exception. Exceptions are prohibited because of the difficulty in formally proving the correctness of a program which raises exceptions.

2.     Lexical Conventions

2.1.  Character sets

2.1.1.     Do not use tab characters in source files

Tab width is not consistent across all editors or tools. Code indentation can be especially confusing when tabs and spaces are used interchangeably. This may easily happen where code is maintained in different editors.
In string and character literals \t should be used in preference to a direct tab character.

Corresponding Ada rule:

Use of tabs in Ada source code is subject to the same editor and tool variations as use of a tab in C++ source code.
Insertion of a tab character in a string or character literal or variable should be use Ada.Characters.Latin_1.HT.

2.2.  Trigraph sequences

2.2.1.     Do not use digraphs or trigraphs

Trigraphs are special three character sequences, beginning with two question marks and followed by one other character. They are translated into specific single characters, e.g. \ or ^. Digraphs are special two character sequences that are similarly translated.

Corresponding Ada rule:

Ada does not provide digraph or trigraph sequences.

2.3.  Comments

2.3.1.     Do not use the C comment delimiters /* … */

The scope of C++ comments is clearer; until the end of a logical source line (taking line splicing into account). Errors can result from the use of C comments.

Corresponding Ada rule:

Ada does not allow the use of C comments.

2.3.2.     Do not comment out code

Source code should always be under version control. Therefore keeping old code in comments is unnecessary. It can make browsing,  searching, and refactoring the source code more difficult.

Corresponding Ada rule:

The same rule should apply to any programming language.

2.4.  Identifiers

2.4.1.     Ensure that each identifier is distinct from any other visible identifier

Similarity of identifiers impairs readability, can cause confusion and may lead to mistakes.
Names should not differ only in case (foo/Foo) or in the use of underscores(foobar/foo_bar). Additionally, certain combinations of characters look very similar.
Note: This rule does not require that an identifier cannot be reused.

Corresponding Ada rule:

Ada identifiers are case-insensitive, thus (foo/Foo) are treated as the same identifier.
Ada compilers identify duplicate definition of identifier within the same scope.

2.5.  Literals

2.5.1.     Do not concatenate strings with different encoding prefixes

The C++ Standard permits string literals with the following encoding prefixes: u, U, u8, L. A program that concatenates a pair of string literals with u8 and L prefixes is ill-formed.
The result of the remaining prefix combinations are all implementation defined. For this reason encoding sequences should not be mixed.

Corresponding Ada rule:

Ada provides multiple character encoding sequences. Each sequence is a distinct type and therefore each string type is unique. Ada does not allow mixing data types within a single array.

2.5.2.     Do not use octal constants (other than zero)

Octal constants are specified with a leading digit 0; therefore, literal 0 is technically an octal constant.
Do not use any other octal literals, as based on unfamiliarity, this could be confusing and error prone.

Corresponding Ada rule:

Ada allows the use of any number base from 2 through 16. A literal of a particular number base is expressed as base#digits#  (e.g 2#111# is the binary equivalent of 7 base 10) except for base 10, which is implied by simply expressing the digits. Therefore, 11 and 011 are interpreted as 11 base 10.

2.5.3.     Use nullptr for the null pointer constant

The 2011 C++ Language Standard introduced the nullptr keyword do denote a null pointer constant.
The NULL macro and constant expression 0 can be used in both pointer contexts and integral contexts. nullptr, however, is only valid for use in pointer contexts and so cannot be unexpectedly used as an integral value.

Corresponding Ada rule:

Ada access types are functionally equivalent to pointers but are not pointers. Ada access types can be set to null, which is not a numerical value and cannot be confused as an integral value. The address of a variable can be set using with address aspect of a variable declaration:
Foo : Integer with Address => 16#3ff#;

3.     Basic Concepts

3.1.  Scope

3.1.1.     Do not hide declarations

Reusing the same identifier for different declarations is confusing and difficult to maintain. If the hiding declaration is later removed, or the identifier is renamed, a compilation error may not be generated, as the declaration that was previously hidden will now be found.
While hidden namespace scope identifiers can still be accessed with a fully qualified name, hidden block scope identifiers will not be accessible.
In C++ it is possible for the same identifier to refer to both a type and an object or a function. In this case the object or function will hide the type.

Corresponding Ada rule:

Use of an identifier in a local scope that matches a visible identifier in a dependent package will result in a compiler error. Use of an identifier in an inner block will hide the use of the same identifier an enclosing block.
Ada does not allow an identifier to refer to both a type and an object or a function within the same or enclosing scopes.

3.2.  Program and linkage

3.2.1.     Do not declare functions at block scope

A declaration for a function should not be common to its definition, any redeclarations, and any calls to it.
To ensure that the same type is used in all declarations, functions should always be declared at namespace scope(See Rules 7.4.3: “Ensure that an object or a function used from multiple translation units is declared in a single header file” and 7.4.1: “Ensure that any objects, functions, or types to be used from a single translation unit are defined in an unnamed namespace in the main source file”).

Corresponding Ada rule:

Ada provides the package construct to enforce name space and encapsulation. Any type, object, subprogram, or task used by more than one compilation unit must be declared within a package. Ada provides no other means to share declarations between compilation units.
Ada does allow a single subprogram or task to define types, objects, subprograms or tasks to be used only within the defining subprogram or task.

3.3.  Storage duration

3.3.1.     Do not use variables with static storage duration

Variables with linkage (and hence static storage duration), commonly referred to as global variables, can be accessed and modified from anywhere in the translation unit if they have internal linkage, and anywhere in the program if they have external linkage. This can lead to uncontrollable relationships between functions and modules.
Additionally, certain aspects of the order of initialization of global variables are unspecified and implementation defined in the C++ Language Standard. This can lead to unpredictable results for global variables that are initialized at run-time (dynamic initialization).
This rule does not prohibit the use of a const object with linkage, so long as:
·         It is initialized through static initialization
·         The object is no ODR used
The order of initialization of block scope objects with static storage is well defined. However, the lifetime of such an object ends at program termination, which may be incompatible with future uses of the code, e.g. as a shared library. It is preferable to use objects with dynamic storage duration to represent program state, allocated from the heap or a memory pool.

Corresponding Ada rule:

The only way to share variables across compilation units in Ada is by declaring those variables within a package. The order of initialization of constants is controlled by package elaboration rules. While Ada does provide default elaboration rules, it also provides elaboration pragmas allowing the programmer to specify elaboration order for packages.

3.4.  Object lifetime

3.4.1.     Do not return a reference to a pointer or an automatic variable defined with the function

The lifetime of a variable with automatic storage duration ends on exiting the enclosing block. If a reference or a pointer to such a variable is returned from a function, the lifetime of the variable will have ended before the caller can access it through the returned handle, resulting in undefined behavior.

Corresponding Ada rule:

When an object is destroyed in Ada, whether the object was created through allocation from a storage pool, or on the program stack, the Finalize procedure for that object is called. There are no exceptions in Ada.
Ada accessibility rules, which prevent dangling references, are written in terms of accessibility levels, which reflect the run-time nesting of masters. As explained in 7.6.1, a master is the execution of a certain construct, such as a subprogram_body. An accessibility level is deeper than another if it is more deeply nested at run time. For example, an object declared local to a called subprogram has a deeper accessibility level than an object declared local to the calling subprogram. The accessibility rules for access types require that the accessibility level of an object designated by an access value be no deeper than that of the access type. This ensures that the object will live at least as long as the access type, which in turn ensures that the access value cannot later designate an object that no longer exists. The Unchecked_Access attribute may be used to circumvent the accessibility rules.
A given accessibility level is said to be statically deeper than another if the given level is known at compile time to be deeper than the other for all possible executions. In most cases, accessibility is enforced at compile time by Legality Rules. Run-time accessibility checks are also used, since the Legality Rules do not cover certain cases involving access parameters and generic packages.

3.4.2.     Do not assign the address of a variable to a pointer with a greater lifetime

The C++ Standard defines 4 kinds of storage duration:
·         Static
·         Thread
·         Automatic
·         Dynamic
The lifetime of objects with the first 3 kinds of storage duration is fixed, respectively:
·         Until program termination
·         Until thread termination
·         Upon exiting the enclosing block
Therefore, undefined behavior will likely occur if an address of a variable with automatic storage duration is assigned to a pointer with static storage duration, or one defined in an outer block. Similarly, for a thread_local  variable aliased to a pointer with static storage duration.
If using high_integrity::thread, then references or pointers with local storage duration should not be passed into threads that have the high_integrity::DETACH property.

Corresponding Ada rule:

Ada accessibility rules, which prevent dangling references, are written in terms of accessibility levels, which reflect the run-time nesting of masters. As explained in 7.6.1, a master is the execution of a certain construct, such as a subprogram_body. An accessibility level is deeper than another if it is more deeply nested at run time. For example, an object declared local to a called subprogram has a deeper accessibility level than an object declared local to the calling subprogram. The accessibility rules for access types require that the accessibility level of an object designated by an access value be no deeper than that of the access type. This ensures that the object will live at least as long as the access type, which in turn ensures that the access value cannot later designate an object that no longer exists. The Unchecked_Access attribute may be used to circumvent the accessibility rules.
A given accessibility level is said to be statically deeper than another if the given level is known at compile time to be deeper than the other for all possible executions. In most cases, accessibility is enforced at compile time by Legality Rules. Run-time accessibility checks are also used, since the Legality Rules do not cover certain cases involving access parameters and generic packages.

3.4.3.     Use RAII for all resources

Objects with non-trivial destructors and automatic storage duration have their destructors called implicitly when they go out of scope. The destructor will be called for both normal control flow and when an exception is thrown.
The same principle does not apply for a raw handle to a resource, e.g. a pointer to allocated memory. By using a manager class, the lifetime of the resource can be correctly controlled, specifically by releasing it in the destructor.
This idiom is known as Resource Allocation Is Initialization (RAII) and the C++ Language Standard provides RAII wrappers for many resources, such as:
·         Dynamically allocated memory, e.g. std::unique.ptr
·         Files, e.g. std::ifstream
·         Mutexes, e.g. std::lock_guard

Corresponding Ada rule:

The Ada language provides the package Ada.Finalization, which defines two data types:
·         Controlled  – which provides the procedures:
o   Initialize
o   Adjust
o   Finalize
·         Limited_Controlled  - which provides the following procedures
o   Initialize
o   Finalize

3.5.  Types

3.5.1.     Do not make any assumptions about the internal representation of a value or an object

Avoid C++ constructs and practices that are likely to make your code non-portable:
·         A union provides a way to alter the type ascribed to a value without changing its representation. This reduces type safety and is usually unnecessary. In general it is possible to create a safe abstraction using polymorphic types.
·         Integer types other than signed / unsigned char have implementation defined size. Do not use integer types directly, instead use size specified typedefs, defined in a common header file, which can be easily adjusted to match a particular platform.
·         Do not mix bitwise arithmetic operations on the same variable, as this it likely to be non-portable between big and little endian architectures.
·         Do not assume the layout of objects in memory, e.g. by comparing pointers to different objects with relational operators, using the offsetof macro, or performing pointer arithmetic with unspecified or implementation defined layout.

Corresponding Ada rule:

Ada also has predefined types. In the case of the predefined integer and floating point types it is advised that the programmer define types or subtypes which better meet their needs. Ada allows the programmer to specify the valid range of values for numeric types, along with specific memory characteristics.
Ada allows the programmer to define the size of an object or a subtype in bits. The size may of an integer, for instance, may not exceed the size of the largest integer representation allowed on the target hardware. The size of a component of a compound type may be specified, and the specific memory layout of record components may be specified. Record components can be less than a word in size, or their size may extend across multiple words.
The size of an object can be interrogate at run time. For example, a packed array of Boolean values will represent each array element in one bit. Each bit can be individually indexed through the standard Ada array indexing notation.

4.     Standard Conversions

4.1.  Array to pointer conversion

When an array is bound to a function parameter of pointer type the array is implicitly converted to a pointer to the first element of the array.
In order to retain the array dimension, the parameter should be changed to a reference type or changed to a user defined type such as std::array.

Corresponding Ada rule:

Ada does not provide array to pointer conversion. An array is a first class type. The dimension and index values of the array are available wherever the array object is visible.

4.2.  Integral conversions

4.2.1.     Ensure that the U suffix is applied to a literal in a context requiring an unsigned integral expression

If a literal is used to initialize a variable of unsigned type, or with an operand of unsigned type in a binary operation, the U suffix should be appended to the literal to circumvent an implicit conversion, and make the intent explicit.

Corresponding Ada rule:

Ada does not provide implicit conversions between data types.  An unsigned type in Ada is known as a modular type. All integer literals in Ada are of the type Universal Integer. A value of type Universal Integer will be implicitly converted to the target type during assignment or in an arithmetic operation. Ada will raise a compile-time error if the literal is not within the range of the target type during an assignment. Ada will raise a run time exception if the result of the arithmetic operation is not within the range of the target type.
The programmer is allowed to define unique numeric data types. There are no implicit conversions between programmer defined data types, even when they two types share the same memory layout. Ada is strongly typed.

4.2.2.     Ensure that data loss does not demonstrably occur in integral expressions

Data loss can occur in a number of contexts:
·         Implicit conversions
·         Type casts
·         Shift operations
·         Overflow in signed arithmetic operations
·         Wraparound in unsigned arithmetic operations
If possible, integral type conversions should be avoided altogether, by performing operations in a uniform type matched to the execution environment.
Where data storage is a concern, type conversions should be localized with appropriate guards (e.g. assertions) to detect data loss.
Similar techniques can be used to guard shift and arithmetic operations, especially where the data is tainted in a security sense, i.e.  a malicious user can trigger data loss with appropriately crafted input data.
Data loss may also occur if high order bits are lost in a left shift operation, or the right hand operator of a shift operator is so large that the resulting value is always 0 or undefined regardless of the value of the left hand operand.
Therefore, appropriate safeguards should be coded explicitly (or instrumented by a tool) to ensure that data loss does not occur in shift operations.
For the purposes of this rule integral to bool conversions are considered to result in data loss as well. It is preferable to use equality or relational operators to replace such type conversions. The C++ Language Standard states that unless the condition of an if, for, while, or do statement had a Boolean type, it will implicitly converted to bool.
Note: An implicit conversion using an operator bool declared as explicit does not violate this rule.

Corresponding Ada rule:

All integral data operations in Ada are protected by range checks. Any violation of a range specification at run time will result in the exception Constraint_Error.
Ada only allows bit shifting operations on unsigned types.
The Ada Boolean type is not numeric, and therefore does not undergo conversion to an integral type. All conditions of an if, for, or while statement must result in a Boolean value.

4.3.  Floating point conversions

The C++ Standard provides 3 floating point types: float, double, long double that, at least conceptually, increase in precision.
Expressions that implicitly or explicitly cause a conversion from long double to float or double, and from double to float should be avoided as they may result in data loss.
When using a literal in a context that requires a type float, use the F suffix, and for consistency use the L suffix in a long double context.

Corresponding Ada rule:

Ada provides a predefined floating point type named float, which should provide at least 6 decimal digits of precision. The programmer is allowed to define custom floating point types with a user specified range and precision.
There is no implicit conversion between floating point types.
Ada also provides the ability to define fixed point types. Fixed point types are implemented as scaled integers. Fixed point types have a constant number of digits after the decimal place, while floating point types have a variable number of digits after the decimal place (or none at all) depending upon the magnitude of the value represented.
There is no implicit conversion between numeric types in Ada.

4.4.  Floating-integral conversions

4.4.1.     Do not convert floating values to integral values except through use of standard functions

An implicit or explicit conversion from a floating to an integral type can result in data loss due to the significant difference in the respective range of values for each type.
Additionally, floating point to integral type conversions are biased as the fractional part is simply truncated instead of being rounded to the nearest integral value. For this reason use one of the standard library functions: std::floor and std::ceil is recommended if a conversion to an integral type is necessary.
Note: A return value of std::floor and std::ceiling is of floating type, and an implicit or explicit conversion of this value to an integral type is permitted.

Corresponding Ada rule:

There are no implicit conversions between Ada numeric types. Every floating point type and subtype has the attributes S’Ceiling and S’Floor defined to calculate the Ceiling and Floor of the floating point value.
If an explicit conversion between a floating point value and an integral value is performed the conversion  will include a range check. If the resulting value is out of range of the integral type the exception Constraint_Error will be raised.

5.     Expressions

5.1. Primary expressions

5.1.1.     Use of symbolic names instead of literal values in code

Use of “magic” numbers and strings in expressions should be avoided in preference to constants with meaningful names.
The use of named constants improves both the readability and maintainability of the code.

Corresponding Ada rule:

Use of constants is recommended over the use of literals.

5.1.2.     Do not rely on the sequence of evaluation within an expression

To enable optimizations and parallelization, the C++ Standard uses a notation of sequenced before, e.g.:
·         Evaluation of a full expression is sequenced before the next full-expression to be evaluated
·         Evaluation operands of an operator are sequenced before evaluation of the operator
·         Evaluation of arguments in a function call are sequenced before the execution of the function
·         For built-in operators &&, ||,  and operator ? evaluation of the first operand is sequenced before evaluation of the other operand(s)
This defines a partial order on evaluations, and where two evaluations are unsequenced with respect to one another, their execution can overlap. Additionally, two evaluations may be indeterminately sequenced, which is similar, except that the execution cannot overlap.
This definition leaves great latitude to a compiler to re-order evaluation of sub-expressions, which can lead to unexpected, and even undefined behavior. For this reason, and to improve readability an expression should not:
·         Have more than one side effect
·         Result in the modification and access of the same scalar object
·         Include a sub-expression that is an assignment operation
·         Include a sub-expression that is a pre- or post-increment/decrement operation
·         Include a built-in comma operator (for overloaded comma operation see Rule 13.2.1: “Do not overload operators with special semantics”)

Corresponding Ada rule:

In Ada assignment is not an expression, and cannot therefore be part of a compound expression. Furthermore, there are no pre- or post-increment/decrement expressions in Ada. These limitations limit the problems associated with violation of Rule 5.1.2.
Ada 2012 does allow the use of “in out” parameters in functions, which can lead to order dependencies. Early versions of Ada prohibited “in out” parameters in functions, allowing only “in” parameters, to avoid this problem. It is still recommended that functions only use “in” parameters except when interfacing with other languages, such as C and C++.

5.1.3.     Use parentheses in expressions to specify the intent of the expression

The effects of precedence and associativity of operators in a complex expression can be far from obvious. To enhance readability and maintainability of code, no reliance on precedence or associativity should be made, by using explicit parentheses, except for:
·         Operands of assignment
·         Any combination of + and operations only
·         Sequence of && operations only
·         Sequence of ||operations only

Corresponding Ada rule:

In every language it is recommended to use explicit parentheses instead of relying upon precedence and associativity rules.
In Ada the logical and operator is and. The logical or operator is or. The equivalent of the C++ && is “and then” and the equivalent of the C++ || is “or else”.

5.1.4.     Do not capture variables implicitly in a lambda

Capturing variables helps document the intention of the author. It also allows for different variables to be captured by copy and captured by reference within the same lambda.
Exception:

It is not necessary to capture objects with static storage duration or constants that are not ODR used.

However, the use of objects with static storage duration should be avoided. See Rule 3.3.1: ”Do not use variables with static storage duration”.

Corresponding Ada rule:

Ada does not provide lamdas. It is possible to pass a subroutine (function or procedure) as a generic parameter.

5.1.5.     Include a (possibly empty) parameter list in every lambda

The lambda-declarator is optional in a lambda expression and results in a closure that can be called without any parameters.
To avoid any visual ambiguity with other C++ constructs, it is recommended to explicitly include ( ), even though it is not strictly required.

Corresponding Ada rule:

Ada does not provide lamdas. It is possible to pass a subroutine (function or procedure) as a generic parameter.

5.1.6.     Do not code side effects into the right-hand operands of: &&, ||, sizeof, typeid or a function passed to condition variable::wait

For some expressions, the side effect of a sub-expression may not be evaluated at all or can be conditionally evaluated, that is, evaluated only when certain conditions are met. For Example:
·         The right-hand operands of the && and || operators are only evaluated if the left hand operand is true and false respectively.
·         The operand of sizeof is never evaluated.
·         The operand of typeid is evaluated only if it is a function call that returns reference to a polymorphic type.
Having visible side effects that do not take place, or only take place under special circumstances makes the code harder to maintain and can also make it harder to achieve a high level of test coverage.
Conditional Variable: wait member

Every time a waiting thread wakes up, it checks the condition. The wake-up may not necessarily happen in direct response to a notification from another thread. This is called a spurious wake. It is indeterminate how many times and when such spurious wakes happen. Therefore it is advisable to avoid using a function with side effects to perform the condition check.

Corresponding Ada rule:

The same rule applies to the Ada “and then” and “or else” logical operations. The right hand side of the “and then” operation is only evaluated if the left hand side is True. The right hand side of the “or else” operation is only evaluated if the left hand side is False.

5.2.  Postfix expressions

5.2.1.     Ensure that pointer or array access is demonstrably within bounds of a valid object

Unlike standard library containers, arrays do not benefit from bounds checking.
Array access can take one of the equivalent forms: *(p + i) or p[i], and will result in undefined behavior, unless p and p + i point to elements of the same array object. Calculating (but not dereferencing) an address one past the last element of the array is well defined also. Note that a scalar object can be considered as equivalent to an array dimensioned to 1.
To avoid undefined behavior, appropriate safeguards should be coded explicitly (or instrumented by a tool), to ensure that array access is within bounds, and that indirection operations (*) will not result in a null pointer dereference.

Corresponding Ada rule:

Ada makes these checks automatically, so there is no need for manually making them or using some separate tool that also needs to be validated.  The compiler will statically check values for array bounds violations and issue a compilation error when a bounds violation is detected. The Ada compiler also automatically inserts bounds checking into array accesses as needed.
Moreover, the range of the index values for an array can be any contiguous range of discrete values. Ada array indexing is not tied to pointer arithmetic and the lowest bound for an array need not be 0. Ada array definitions include a definition of the array type and range as well as the element type. Arrays have well defined attributes available to the programmer wherever the array object is visible.
Table 1 Attributes of array A
Array Attribute
Description
A’Length
The number of elements in the array
A’First
The value of the lowest index value for the array
A’Last
The value of the highest index value for the array
A’Range
The range of index values for the array; equivalent to A’First..A’Last
Iterating through an array safely in Ada is done using the Ada for loop:
For loop syntax
Description
for I in A’Range loop

   … do something

end loop;

Iterate through the index values for the range of array A, doing something for each index value.
for value of A loop

  … do something

end loop;

Iterate through array A referencing each value of A in turn. This is equivalent to a “for each” loop in some other languages

5.2.2.     Ensure that functions do not call themselves, either directly or indirectly

As the program stack tends to be one of the more limited resources, excessive use of recursion may limit the scalability and portability of the program. Tail recursion can readily be replaced with a loop. Other forms of recursion can be replaced with an iterative algorithm and worklists.

Corresponding Ada rule:

If you want to enforce this rule in Ada you can use the profile No_Recursion.

5.3.  Unary expressions

5.3.1.     Do not apply unary minus to operands of unsigned type

The result of applying a unary minus operator (-) to an operand of unsigned type (after integral promotion) is a value that is unsigned and typically very large.
Prefer to use the bitwise complement (~) operator instead.

Corresponding Ada rule:

The rule here is the same for Ada. The result for an Ada unsigned type is well defined, but may not be what the programmer intends.

5.3.2.     Allocate memory using new and release it using delete

C style allocation is not type safe, and does not invoke constructors or destructors. For this reason only operators new and delete should be used to manage objects with dynamic storage duration.
Note: Invoking delete on a pointer allocated with malloc or invoking free on a pointer allocated with new will result in undefined behavior.

Corresponding Ada rule:

All Ada allocation / deallocation mechanisms are type safe.

5.3.3.     Ensure that the form of delete matches the form of new used to allocate the memory

The C++ Standard requires that the operand to the delete operator is either:
·         a null pointer
·         pointer to a non array object allocated with new
·          pointer to a base class3 subobject of a non array object allocated with new
Similarly, the operand to the delete[] operator is either:
·         a null pointer
·         pointer to an array object allocated with new[]
In order to avoid undefined behavior, plain and array forms of delete and new should not be mixed.

Corresponding Ada rule:

Ada does not have this problem.
There are no special forms of new for allocating arrays in Ada.  An Ada array is a first class type and the compiler knows the size of an array. The new operator allocates the memory needed to hold an object of the type specified.
Ada does requires the generic function Ada.Unchecked_Deallocation be instantiated for the specific type to be deallocated. The generic parameters for this function are the access type and the base type being deallocated.

5.4.  Explicit type conversion

5.4.1.     Only use casting forms: static_cast (excl, void*), dynamic_cast or explicit constructor call

All casts result in some degree of type punning, however, some casts may be considered more error prone than others:
·         It is undefined behavior for the result of a static cast to void* to be cast to any type other than the original from type.
·         Depending on the type of an object, casting away const or volatile and attempting to write to the result is undefined behavior.
·         Casts using reinterpret cast are generally unspecified and/or implementation defined. Use of this cast increases the effort required to reason about the code and reduces its portability.
·         Simplistically, a C-style cast and a non class function style cast can be considered as a sequence of the other cast kinds. Therefore, these casts suffer from the same set of problems. In addition, without a unique syntax, searching for such casts in code is extremely difficult.

Corresponding Ada rule:

Ada uses the term type conversion when converting a value from one type to another type. There is only one syntax for type conversion:  subtype-name (expression-or-identifier)
If the target type of a conversion is an integer type and the operand is a real type then the result is the result rounded to the nearest integer (away from zero if exactly half way between two integers). Range checking is done after the converted value has been calculated.

5.4.2.     Do not cast an expression to an enumeration type

The result of casting an integer to an enumeration type is unspecified if the value is not within the range of the enumeration. This also applies when casting between different enumeration types.
For this reason conversions to an enumeration type should be avoided.

Corresponding Ada rule:

Converting between an integer and an enumeration value is not performed by type conversion. Instead, enumeration types have the attributes T’Pos and T’Val. T’Pos takes an argument of the enumeration type and returns the position number of that argument. T’Val takes an integer as an argument and returns the value of type T whose position number equals the value of the argument. If there is no value with the given position number the exception Constraint_Error is raised.

5.4.3.      Do not convert from a base class to a derived class

The most common reason for casting down an inheritance hierarchy, is to call derived class methods on an object that is a reference or pointer to the base class.
Using a virtual function removes the need for the cast completely and improves the maintainability of the code.

Corresponding Ada rule:

In Ada casting between levels of an inheritance hierarchy is called a view conversion. Converting from a base type to a derived type is valid only when the derived type does not add any data members to the set of members defined in the base type.
In Ada polymorphic types are called tagged types.

5.5.  Multiplicative operators

5.5.1.     Ensure that the right hand operand of the division or remainder operations is non-zero

The result of integer division or remainder operation is undefined if the right hand operand is zero. Therefore, appropriate safeguards should be coded explicitly (or instrumented by a tool) to ensure that division by zero does not occur.

Corresponding Ada rule:

The result of division by zero is well defined in Ada. The result will cause the exception Constraint_Error to be raised.

5.6.  Shift operators

5.6.1.     Do not use bitwise operators with signed operands

Use of signed operands with bitwise operators is in some cases subject to undefined or implementation defined behavior. Therefore, bitwise operators should only be used with operands of unsigned integral types.

Corresponding Ada rule:

In Ada the bitwise operators are only defined for unsigned types.

5.7.  Equality operators

5.7.1.     Do not write code that expects floating point calculations to yield exact results

Floating point calculations suffer from machine precision (epsilon), such that the exact result may not be representable.
Epsilon is defined as the difference between 1 and the smallest value greater than 1 that can be represented in a given floating point type. Therefore, comparisons of floating point values need to take epsilon into account.

Corresponding Ada rule:

Floating point number exhibit the same behavior in Ada. If you want exact equality in a real number then use a fixed point representation instead of a floating point representation.

5.7.2.     Ensure that a pointer to a member that is a virtual function is only compared (==) with nullptr

The result of comparing a pointer to member to a virtual function to anything other than nullptr is unspecified.

Corresponding Ada rule:

Ada polymorphism does not depend upon pointers to members of a class hierarchy. Parameters to subprograms can be defined as class-wide parameters, and the actual parameter can be any object with the class hierarchy.

5.8.  Conditional operator

5.8.1.     Do not use the conditional operator (?:) as a sub-expression

Evaluation of a complex condition is best achieved through explicit conditional statements (if/else). Using the result of the conditional operator as an operand reduces the maintainability of the code.
The only permissible uses of a conditional expression are:
·         argument expression in a function call
·         return expression
·          initializer in a member initialization list
·         object initialize
·         the right hand side operand of assignment (excluding compound assignment)
The last use is allowed on the basis of initialization of an object with automatic storage duration being equivalent to its declaration, followed by assignment.

Corresponding Ada rule:

Ada2012 provides two forms of conditional expression; the conditional if statement and the conditional case statement:

conditional_expression ::= if_expression | case_expression

    if_expression ::= 
   if 
condition then dependent_expression
   {elsif 
condition then dependent_expression}
   [else dependent_
expression]

condition ::= boolean_expression

   case_expression ::= 
    case selecting_
expression is
    
case_expression_alternative {,
    
case_expression_alternative}

case_expression_alternative ::= 
    when 
discrete_choice_list =>
        dependent_
expression

Wherever the Syntax Rules allow an expression, a conditional_expression may be used in place of the expression, so long as it is immediately surrounded by parentheses.

Note: In Ada assignment is not an expression. Each dependent expression of a condtional expression must be the type of the conditional expression.

6.     Statements

6.1.  Selection statements

6.1.1.     Enclose the body of a selection or an iteration statement in a compound statement

Follow each control flow primitive (if, else, while, for, do and switch) by a block enclosed by braces, even if the block is empty or contains only one line. Use of null statements or statement expressions in these contexts reduces code readability and making it harder to maintain.

Corresponding Ada rule:

All Ada selection or iteration statements are always fully bracketed compound statements.

6.1.2.     Explicitly cover all paths through multi-way selection statements

Make sure that each if-else-if chain has a final else clause, and every switch statement has a default clause.
The advantage is that all execution paths are explicitly considered, which in turn helps reduce the risk that an unexpected value will result in incorrect execution.

Corresponding Ada rule:

The rule for if-elsif-else in Ada applies.
Ada case statements require all possible values of the selection type to be covered. If some value or values are not covered explicitly then an others case must be included. The compiler will issue an error if this rule is not followed.

6.1.3.     Ensure that a non-empty case statement block does not fall through to the next label

Fall through from a non empty case block of a switch statement makes it more difficult to reason about the code, and therefore harder to maintain.

Corresponding Ada rule:

Ada case statements do not exhibit fall-through.

6.1.4.     Ensure that a switch statement has at least two case labels, distinct from the default label

A switch statement with fewer than two case labels can be more naturally expressed as a single if statement.

Corresponding Ada rule:

Ada case statements are intended to handle all the values of a discrete type, not just one. A simple if statement is appropriate if only one value of a discrete range of values is being tested.

6.2.  Iteration statements

6.2.1.     Implement a loop that only uses element values as a range-based loop

A range-based for statement reduces the amount of boilerplate code required to maintain correct loop semantics.
A range-based loop can normally replace an explicit loop where the index or iterator is only used for accessing the container value.

Corresponding Ada rule:

The traditional Ada for loop always iterates over a range of values. The Ada iterator loop iterates through the values of all standard Ada containers and all Ada arrays.

6.2.2.     Ensure that a loop has a single loop counter, an optional control variable, and is not degenerate

A loop is considered ’degenerate’ if:
·         when entered, the loop is infinite, or
·         the loop will always terminate after the first iteration.
To improve maintainability it is recommended to avoid degenerate loops and to limit them to a single counter variable.

Corresponding Ada rule:

Ada provides four kinds of loops.
·         The simple loop is the most general loop and can be used for loops that test termination at the beginning of the loop, at the end of the loop, or in the middle of the loop
·         The while loop tests a control expression at the top of the loop
·         The for loop executes for each value in a specified range
·         The iterator loop executes for every member of a container or array
Ada does not provide a direct equivalent of the C++ for loop.

6.2.3.     Do not alter the control or counter variable more than once in a loop

The behavior of iteration statements with multiple modifications of control or counter variables is difficult to understand and maintain.

Corresponding Ada rule:

The Ada for loop has a counter which is modified only at the top of the loop, and is read-only within the body of the loop. The counter cannot be modified more than once per loop iteration.

6.2.4.     Only modify a for loop counter in the for expression

It is expected that a for loop counter is modified for every iteration. To improve code readability and maintainability, the counter variable should be modified in the loop expression.

Corresponding Ada rule:

The Ada for loop has a counter which is modified only at the top of the loop, and is read-only within the body of the loop. The counter cannot be modified more than once per loop iteration.

6.3.  Jump statements

6.3.1.     Ensure that the label(s) for a jump statement or a switch condition appear later, in the same or an enclosing block

Backward jumps and jumps into nested blocks make it more difficult to reason about the flow through the function.
Loops should be the only constructs that perform backward jumps, and the only acceptable use of a goto statement is to jump forward to an enclosing block.
Control can also be transferred forward into a nested block by virtue of a switch label. Unless case and default labels are placed only into the top level compound statement of the switch, the code will be difficult to understand and maintain.

Corresponding Ada rule:

Ada case statements do not allow selection of a value within an if statement.
Ada loops can be labeled, and the loop exit statement in an inner loop can identify the label of an outer loop, allowing a direct exit from the outer loop controlled by a condition in an inner loop. This ability removes the need to set flag variables throughout a nesting of loops with associated testing of the flag. The advantage is code simplicity and added readability.

6.3.2.     Ensure that execution of a function with a non-void return type ends in a return statement with a value

Undefined behavior will occur if execution of a function with a non void return type (other than main) flows off the end of the function without encountering a return statement with a value.
Exception:

The main function is exempt from this rule, as an implicit return 0; will be executed, when an explicit return statement is missing.

Corresponding Ada rule:

Ada provides two kinds of subprograms. Procedures never return a value. Functions always return a value. The return value of a function must always be handled by the calling subprogram. The Ada compiler will issue an error if a function does not return a value of the specified return type, or if a procedure attempts to return a value of any type.
The Ada main subprogram is always a procedure and returns no value.

6.4.  Declaration statement

To preserve locality of reference, variables with automatic storage duration should be defined just before they are needed, preferably with an initializer, and in the smallest block containing all the uses of the variable.
The scope of a variable declared in a for loop initialization statement extends only to the complete for statement.
Therefore, potential use of a control variable outside of the loop is naturally avoided.

Corresponding Ada rule:

Ada only allows variables, constants, subprograms, or tasks to be defined in the declaration section of a block. The one exception is the for loop control variable which is defined at the start of the for loop and is not visible outside the for loop.

7.     Declarations

7.1.  Specifiers

7.1.1.     Declare each identifier on a separate line in a separate declaration

Declaring each variable or typedef on a separate line makes it easier to find the declaration of a particular identifier.
Determining the type of a particular identifier can become confusing for multiple declarations on the same line.
Exception:

For loop initialization statement is exempt from this rule, as in this context the rule conflicts with Rule 6.4.1: ”Postpone variable definitions as long as possible”, which takes precedence.

Corresponding Ada rule:

Ada only allows variables, constants, subprograms, or tasks to be defined in the declaration section of a block. The one exception is the for loop control variable which is defined at the start of the for loop and is not visible outside the for loop.

7.1.2.     Use const whenever possible

This allows specification of semantic constraint which a compiler can enforce. It explicitly communicates to other programmers that value should remain invariant. For example, specify whether a pointer itself is const, the data it points to is const, both or neither.
Exception:

By-value return types are exempt from this rule. These should not be const as doing so will inhibit move semantics.

Corresponding Ada rule:

Ada allows an object to be defined as constant, but does not have the complex const rules of C++. When passing parameters to a subprogram the passing mode can be defined as IN, which forces the subprogram to view that parameter as a constant. Parameters passed with the IN mode are read-only within the subprogram they are passed to.

7.1.3.     Do not place type specifiers before non-type specifiers in  declaration

The C++ Standard allows any order of specifiers in a declaration. However, to improve readability if a non-type specifier (typedef, friend, constexpr, register, static, extern, thread local, mutable, inline, virtual, explicit) appears in a declaration, it should be placed leftmost in the declaration.

Corresponding Ada rule:

Variable declarations always take the form of
object_declaration ::= 
    
defining_identifier_list : [aliased] [constantsubtype_indication [:= expression]
        [
aspect_specification];
  | 
defining_identifier_list : [aliased] [constantaccess_definition [:= expression]
        [
aspect_specification];
  | 
defining_identifier_list : [aliased] [constantarray_type_definition [:= expression]
        [
aspect_specification];
  | 
single_task_declaration
  | 
single_protected_declaration

defining_identifier_list ::= 
  
defining_identifier {, defining_identifier}

7.1.4.     Place CV-qualifiers on the right hand side of the type they apply to

The const or volatile qualifiers can appear either to the right or left of the type they apply to. When the unqualified portion of the type is a typedef name (declared in a previous typedef declaration), placing the CV-qualifiers on the left hand side, may result in confusion over what part of the type the qualification applies to.
For consistency, it is recommended that this rule is applied to all declarations.

Corresponding Ada rule:

Variable declarations always take the form of
object_declaration ::= 
    
defining_identifier_list : [aliased] [constantsubtype_indication [:= expression]
        [
aspect_specification];
  | 
defining_identifier_list : [aliased] [constantaccess_definition [:= expression]
        [
aspect_specification];
  | 
defining_identifier_list : [aliased] [constantarray_type_definition [:= expression]
        [
aspect_specification];
  | 
single_task_declaration
  | 
single_protected_declaration

defining_identifier_list ::= 
  
defining_identifier {, defining_identifier}

7.1.5.     Do not inline large functions

The definition of an inline function needs to be available in every translation unit that uses it. This in turn requires that the definitions of inline functions and types used in the function definition must also be visible.
The inline keyword is just a hint, and compilers in general will only inline a function body if it can be determined that performance will be improved as a result.
As the compiler is unlikely to inline functions that have a large number of statements and expressions, inlining such functions provides no performance benefit but will result in increased dependencies between translation units.
Given an approximate cost of 1 for every expression and statement, the recommended maximum cost for a function is 32.

Corresponding Ada rule:

The inline aspect of a subprogram is optional for a compiler. It notifies the compiler of the programmer’s desire that the subprogram be inlined.

7.1.6.     Use class types or typedefs to abstract scalar quantities and standard integer types

Using class types to represent scalar quantities exploits compiler enforcement of type safety. If this is not possible, typedefs should be used to aid readability of code.
Plain char type should not be used to define a typedef name, unless the type is intended for parameterizing the code for narrow and wide character types. In other cases, an explicit signed char or unsigned char type should be used in a typedef as appropriate.
To enhance portability, instead of using the standard integer types (signed char, short, int, long, long long, and the unsigned counterparts), size specific types should be defined in a project-wide header file, so that the definition can be updated to match a particular platform (16, 32 or 64bit). Where available, intN t and uintN t types (e.g. int8 t) defined in the cstdint header file should be used for this purpose.
Where the auto type specifier is used in a declaration, and the initializer is a constant expression, the declaration should not be allowed to resolve to a standard integer type. The type should be fixed by casting the initializer to a size specific type.
Exception:

The C++ Language Standard places type requirements on certain constructs. In such cases, it is better to use required type explicitly rather than the typedef equivalent which would reduce the portability of the code.
The following constructs are therefore exceptions to this rule:
·         int main()

·         T operator++(int)

·         T operator–(int)

Corresponding Ada rule:

Ada allows the programmer to create simple and detailed definitions of scalar types and subtypes. New integer types can be simply defined by creating a type name and specifying the valid range of values for the type, or they can be derived from an existing type with a restriction on the range of valid values.

For example:

type Counts is range 0..100;

type Normalized is new Integer range -100..100;

Ada also allows the programmer to define subtypes of existing types. Objects of a subtype are members of the base type, usually with a restricted range of valid values.

subtype Natural is Integer range 0..Integer’Last;

subtype Positive is Integer range 1..Integer’Last;

Subtypes can be used in mixed calculations with any other subtypes of the same base type. Objects of different base types cannot be used in an expression without explicit conversion to a common type.

7.1.7.     Use a trailing return type in preference to type disambiguation using typename

When using a trailing return type, lookup for the function return type starts from the same scope as the function declarator. In many cases, this will remove the need to specify a fully qualified return type along with the typename keyword.

Corresponding Ada rule:

Ada has no equivalent of a trailing return type.

7.1.8.     Use auto id = expr when declaring a variable to have the same type as its initializer function call

When declaring a variable that is initialized with a function call, the type is being specified twice. Initially on the return of the function and then in the type of the declaration.
Using auto and implicitly deducing the type of the initializer will ensure that a future change to the declaration of foo will not result in the addition of unexpected implicit conversions.

Corresponding Ada rule:

This rule is contrary to Ada strong typing. Every variable must be declared to be definite, even if it is a member of an indefinite type. The type returned by an initializing function must match the type of the object being declared.
An object of an indefinite type must be initialized to a definite object. For instance, the type String is an unconstrained array of character. An unconstrained array is an indefinite type. Each instance of a String must have a specified length, which is defined when the String object is initialized. The String object is definite. The String type is indefinite.

7.1.9.     Do not explicitly specify the return type of a lambda

Allowing the return type of a lambda to be implicitly deduced reduces the danger of unexpected implicit conversions, as well as simplifying future maintenance, where changes to types used in the lambda would otherwise result in the need to change the return type.

Corresponding Ada rule:

Ada does not allow lambdas.

7.1.10.                        Use static_assert for assertions involving compile time constants

A static assert will generate a compile error if its expression is not true. The earlier that a problem can be diagnosed the better, with the earliest time possible being as the code is written.

Corresponding Ada rule:

When defining a subtype the programmer should use either a Dynamic_Predicate or a Static_Predicate if the use of a simple range cannot fully define the characteristics of the subtype.
A Static_Predicate expression must be one of
·         A static membership test where the choice is selected by the current instance
·         A case expression whose dependent expressions are static and selected by the current instance
·         A call of the predefined operations =, /=, <, <=, >, >= where one operand is the current instance
·         An ordinary static expression
A Dynamic_Expression can be any Boolean expression.
Examples:

subtype Even is Integer

   with Dynamic_Predicate => Even mod 2 = 0;


subtype Letter is Character

   with Static_Predicate => Letter in ‘A’..’Z’|’a’..’z’;

7.2.  Enumeration declarations

7.2.1.     Use an explicit enumeration base and ensure that it is large enough to store all enumerations

The underlying type of an unscoped enumeration is implementation defined, with the only restriction being that the type must be able to represent the enumeration values. An explicit enumeration base should always be specified with a type that will accommodate both the smallest and the largest enumerator.
A scoped enum will implicitly have an underlying type of int, however, the requirement to specify the underlying type still applies.
Exception:

An enumeration declared in an extern "C" block (i.e. one intended to be used with C) does not require an explicit underlying type.

Corresponding Ada rule:

Ada enumerations are not numeric types and cannot be converted to numeric types. The compiler chooses the size of the enumeration representation base upon the number of enumeration values.

7.2.2.     Initialize none, the first only, or all enumerators in an enumeration

It is error prone to initialize explicitly only some enumerators in an enumeration, and to rely on the compiler to initialize the remaining ones. For example, during maintenance it may be possible to introduce implicitly initialized enumerators with the same value as an existing one initialized explicitly.
Exception:

When an enumeration is used to define the size and to index an array, it is acceptable and recommended to define three additional enumerators after all other enumerators, to represent the first and the last elements, and the size of the array.

Corresponding Ada rule:

When specifying the enumeration representation all enumerated values must be specified.
Example:
type Mix_Code is (ADD, SUB, MUL, LDA, STA, STZ);

for Mix_Code use (ADD => 1, SUB => 2, MUL => 3, LDA => 8, STA => 24, STZ =>33);

7.3.  Namespaces

7.3.1.     Do not use using directives

Namespaces are an important tool in separating identifiers and in making interfaces explicit.
A using directive, i.e. using namespace, allows any name to be searched for in the namespace specified by the using directive.
A using declaration, on the other hand, brings in a single name from the namespace, as if it was declared in the scope containing the using declaration.

Corresponding Ada rule:

The unit of encapsulation in Ada is the package. Packages provide the name space designation of C++ namespaces.
In Ada one can either use the entire package, use only a specific type defined in the package, or explicitly append the package name to each element accessed from the package.
Any naming ambiguity will be identified by the compiler.

7.4.  Linkage specifications

7.4.1.     Ensure that any objects, functions or types to be used from a single translation unit are defined in an unnamed namespace in the main source file

Declaring an entity in an unnamed namespace limits its visibility to the current translation unit only. This helps reduce the risk of name clashes and conflicts with declarations in other translation units.
It is preferred to use unnamed namespaces rather than the static keyword to declare such entities.

Corresponding Ada rule:

Each compilation unit specifies its own dependencies in its dependency clauses. There is no visibility to compilation units not defined in the dependency clause.

7.4.2.     Ensure that an inline function, a function template, or a type used from multiple translation units is defined in a single header file

An inline function, a function template or a user defined type that is intended for use in multiple translation units should be defined in a single header file, so that the definition will be processed in exactly the same way (the same sequence of tokens) in each translation unit.
This will ensure that the one definition rule is adhered to, avoiding undefined behavior, as well as improving the maintainability of the code.

Corresponding Ada rule:

Each subprogram, generic compilation unit, type, task, task type, protected object, or protected type is defined uniquely in a compilation unit. Dependency upon compilation units which define types or objects with overlapping identifiers results in ambiguity which is flagged as a compilation error and must be corrected before an executable is produced by the compilation process.

7.4.3.     Ensure that an object or a function used from multiple translation units is declared in a single header file

An object or function with external linkage should be declared in a single header file in the project.
This will ensure that the type seen for an entity in each translation unit is the same thereby avoiding undefined behavior.

Corresponding Ada rule:

Objects used by multiple compilation units must be declared in the public region of a package. Functions and procedures can either be declared in a package or in a stand-alone compilation unit.

7.5.  The asm declaration

7.5.1.     Do not use the asm declaration

Use of inline assembly should be avoided since it restricts the portability of the code.

Corresponding Ada rule:

Machine code insertions should be avoided since they restrict the portability of the code.

8.     Definitions

8.1. Type names

8.1.1.     Do not use multiple levels of pointer indirection

In C++, at most one level of pointer indirection combined with references is sufficient to express any algorithm or API.
Instead of using multidimensional arrays, an array of containers or nested containers should be used. Code reliant on more than one level of pointer indirection will be less readable and more difficult to maintain.

Corresponding Ada rule:

Use of multidimensional arrays is allowed, or one can choose containers or nested containers. Ada arrays do not employ pointer indirection at the source code level.

8.2. Meanings of declarators

8.2.1.     Make parameter names absent or identical in all declarations

Although the C++ Standard does not mandate that parameter names match in all declarations of a function (e.g. a declaration in a header file and the definition in the main source file), it is good practice to follow this principle.

Corresponding Ada rule:

All subprogram parameter names must be declared and must match the corresponding subprogram implementation.
Subprogram parameters may be referenced by position or by name when the subprogram is called. It is recommended to use named notation when calling a subprogram. When using named notation the actual parameter is explicitly matched with the formal parameter, and the order of parameters in the called subprogram is not relevant.
Example:
Procedure Get_Line(Item : out String; Last : out Natural);

Length : Natural;

Input : String(1..256);

Get_Line(Item => Input, Last => Length);

8.2.2.     Do not declare functions with excessive number of parameters

A function defined with a long list of parameters often indicates poor design and is difficult to read and maintain.
The recommended maximum number of function parameters is six.

Corresponding Ada rule:

While it is seldom useful to declare a subprogram with a large number of parameters, the problem is greatly relieved through the use of named notation.

8.2.3.     Pass small objects with a trivial copy constructor by value

Because passing by const reference involves an indirection, it will be less efficient than passing by value for a small object with a trivial copy constructor.

Corresponding Ada rule:

Ada parameters have an associated mode indicating data flow. The modes are IN, OUT, IN OUT. The compiler determines whether a particular parameter should be passed by copy or by reference.

8.2.4.     Do not pass std::unique_ptr by const reference

An object of type std::unique ptr should be passed as a non-const reference, or by value. Passing by non-const reference signifies that the parameter is an in/out parameter. Passing by value signifies that the parameter is a sink (i.e. takes ownership and does not return it).
A const reference std::unique ptr parameter provides no benefits and restricts the potential callers of the function.

Corresponding Ada rule:

Ada parameter modes explicitly state whether the parameter is passed IN, OUT, or IN OUT.

8.3.  Function definitions

8.3.1.     Do not write functions with excessive McCabe Cyclomatic Complexity

The McCabe Cyclomatic Complexity is calculated as the number of decision branches within a function plus 1.
Complex functions are hard to maintain and test effectively. It is recommended that the value of this metric does not exceed 10.

Corresponding Ada rule:

Excessive cyclomatic complexity has been shown to produce code that is difficult to understand and maintain. It is often an indication of poor code design.

8.3.2.     Do not write functions with a high static program path count

Static program path count is the number of non-cyclic execution paths in a function. Functions with a high number of paths through them are difficult to test, maintain and comprehend. The static program path count of a function should not exceed 200.

Corresponding Ada rule:

High static path count is an indication of poor software design. All code should be testable and maintainable.

8.3.3.     Do not use default arguments

Use of default arguments can make code maintenance and refactoring more difficult. Overloaded forwarding functions can be used instead without having to change existing function calls.

Corresponding Ada rule:

Overloaded forwarding functions provide no maintenance benefit. Use of default arguments and named notation for calling subprograms clearly documents all calls for maintenance purposes without creating a plethora of overloaded subprograms.

8.3.4.     Define =delete functions with parameters of rvalue reference to const

A simple model for an rvalue reference is that it allows for the modification of a temporary. A const rvalue reference therefore defeats the purpose of the construct as modifications are not possible.
However, one valid use case is where the function is defined =delete. This will disallow the use of an rvalue as an argument to that function.

Corresponding Ada rule:

Ada has no equivalent to defining a subprogram as =delete.

8.4.  Initializers

8.4.1.     Do not access an invalid object or an object with indeterminate value

A significant component of program correctness is that the program behavior should be deterministic. That is, given the same input and conditions the program will produce the same set of results.
If a program does not have deterministic behavior, then this may indicate that the source code is reliant on unspecified or undefined behavior.
Such behaviors may arise from use of:
·         variables not yet initialized
·         memory (or pointers to memory) that has been freed
·         moved from objects

Corresponding Ada rule:

The Ada compiler identifies when the value of a variable is used before the variable is initialized or assigned a value. The SPARK subset of Ada requires all variables to be fully initialized.

8.4.2.     Ensure that a braced aggregate initialize matches the layout of the aggregate object

If an array or a struct is non-zero initialized, initializers should be provided for all members, with an initializer list for each aggregate (sub)object enclosed in braces. This will make it clear what value each member is initialized with.

Corresponding Ada rule:

Aggregates can be constructed for arrays or records. While aggregates can be constructed using positional notation, similar to C++, named notation should always be used.
Use of named notation will ensure and document that all elements of the composite type have been initialized.

9.     Classes

9.1.  Member functions

9.1.1.     Declare static any member function that does not require this. Alternatively, declare const any member function that does not modify the externally visible state of the object

A non-virtual member function that does not access the this pointer can be declared static. Otherwise, a function that is virtual or does not modify the externally visible state of the object can be declared const.
The C++ language permits that a const member function modifies the program state (e.g. modifies a global variable, or calls a function that does so). However, it is recommended that const member functions are logically const also, and do not cause any side effects.
The mutable keyword can be used to declare member data that can be modified in a const function, however, this should only be used where the member data does not affect the externally visible state of the object.

Corresponding Ada rule:

Packages are the unit of encapsulation in Ada. Tagged types are polymorphic types in Ada. Tagged types can be defined in a package, making them available to multiple compilation units. Tagged types can have primitive operations, which correspond to virtual member functions in C++. A subprogram is primitive to some tagged type T if all of the following are true:
·         The subprogram is declared in the visible part of the package in which tagged type T is declared
·         The subprogram has a parameter of type T, or an access parameter pointing to an instance of type T, or is a function returning a result of type T
Any subprogram which is declared in the package where type T is declared, and does not meet the above requirements, is not a primitive of type T. Such a subprogram is equivalent to a C++ static member function. Any procedure with only an parameter of IN mode of type T is equivalent to a C++ const member function.

9.1.2.     Make default arguments the same or absent when overriding a virtual function

The C++ Language Standard allows that default arguments be different for different overrides of a virtual function.
However, the compiler selects the argument value based on the static type of the object used in the function call.
This can result in confusion where the default argument value used may be different to the expectation of the user.

Corresponding Ada rule:

The Ada Language Standard requires  overridden subprograms be conformant in their use of default parameter expressions. When a subprogram overrides another subprogram the parameter profile cannot change regarding default expressions.

9.1.3.     Do not return non-const handles to class data from const member functions

A pointer or reference to non-const data returned from a const member function may allow the caller to modify the state of the object. This contradicts the intent of a const member function.

Corresponding Ada rule:

A procedure with an IN mode parameter  cannot modify the data passed to it, nor can it return a value of any kind.

9.1.4.     Do not write member functions which return non-const handles to data less accessible than the member function

Member data that is returned by a non-const handle from a more accessible member function, implicitly has the access of the function and not the access it was declared with. This reduces encapsulation and increases coupling.
Exception:

Non-const operator [] is exempt from this rule, as in this context the rule conflicts with Rule 13.2.4: ”When overloading the subscript operator (operator[]) implement both const and non-const versions”, which takes precedence.

Corresponding Ada rule:

Ada functions need not return pointers to complex data. Instead they can return entire complex objects including records and arrays. This avoids the lifetime issues associated with C++ member functions.

9.1.5.     Do not introduce virtual functions in a final class

Declaring a class as final explicitly documents that this is a leaf class as it cannot be used as a base class.
Introducing a virtual function in such a class is therefore redundant as the function can never be overridden in a derived class.

Corresponding Ada rule:

Ada does not explicitly label a tagged type “final”. Instead, if no subprograms with class-wide parameters are introduced the tagged type has the effect of being “final”.

9.2.  Bit-fields

9.2.1.     Declare bit-fields with an explicitly unsigned integral or enumeration type

To avoid reliance on implementation defined behavior, only declare bit-fields of an explicitly unsigned type (uintN t) or an enumeration type with an enumeration base of explicitly unsigned type.

Corresponding Ada rule:

One must define data types with value ranges that can fit into the specified bit layout of a record. Failure to do so will result in a compiler error message.
Example:
Word : constant := 4;  --  storage element is byte, 4 bytes per word

type State         is (A,M,W,P);
type Mode          is (Fix, Dec, Exp, Signif);

type Byte_Mask     is array (0..7)  of Boolean;
type State_Mask    is array (State) of Boolean;
type Mode_Mask     is array (Mode)  of Boolean;

type Program_Status_Word is
  record
      System_Mask        : Byte_Mask;
      Protection_Key     : Integer range 0 .. 3;
      Machine_State      : State_Mask;
      Interrupt_Cause    : Interruption_Code;
      Ilc                : Integer range 0 .. 3;
      Cc                 : Integer range 0 .. 3;
      Program_Mask       : Mode_Mask;
      Inst_Address       : Address;
end record;

for Program_Status_Word use
  record
      System_Mask      at 0*Word range 0  .. 7;
      Protection_Key   at 0*Word range 10 .. 11; --
 bits 8,9 unused
      Machine_State    at 0*Word range 12 .. 15;
      Interrupt_Cause  at 0*Word range 16 .. 31;
      Ilc              at 1*Word range 0  .. 1;  --
 second word
      Cc               at 1*Word range 2  .. 3;
      Program_Mask     at 1*Word range 4  .. 7;
      Inst_Address     at 1*Word range 8  .. 31;
  end record;

for Program_Status_Word'Size use 8*System.Storage_Unit;
for Program_Status_Word'Alignment use 8;

10.           Derived classes

10.1.                 Multiple base classes

10.1.1.                        Ensure that access to base class subobjects does not require explicit disambiguation

A class inherited more than once in a hierarchy, and not inherited virtually in all paths will result in multiple base class subobjects being present in instances of the derived object type.
Such objects require that the developer explicitly select which base class to use when accessing members. The result is a hierarchy that is harder to understand and maintain.

Corresponding Ada rule:

Ada allows multiple inheritance only of interfaces, not of tagged types. All subprograms in interfaces are abstract, corresponding to virtual inheritance of multiple base classes in C++.

10.2.                 Virtual functions

10.2.1.                        Use the override special identifier when overriding a virtual function

The override special identifier is a directive to the compiler to check that the function is overriding a base class member. This will ensure that a change in the signature of the virtual function will generate a compiler error.

Corresponding Ada rule:

An overriding_indicator is used to declare that an operation is intended to override (or not override) an inherited operation. 

Syntax

overriding_indicator ::= [notoverriding

Legality Rules

·        the operation shall be a primitive operation for some type;

·        if the overriding_indicator is overriding, then the operation shall override a homograph at the place of the declaration or body;

·        if the overriding_indicator is not overriding, then the operation shall not override any homograph (at any place). 

In addition to the places where Legality Rules normally apply, these rules also apply in the private part of an instance of a generic unit.

10.3.                 Abstract classes

10.3.1.                        Ensure that a derived class has at most one base class which is not an interface class

An interface class has the following properties:
·         all public functions are pure virtual functions or getters, and
·         there are no public or protected data members, and
·          it contains at most one private data member of integral or enumerated type
Inheriting from two or more base classes that are not interfaces, is rarely correct. It also exposes the derived class to multiple implementations, with the risk that subsequent changes to any of the base classes may invalidate the derived class.
On the other hand. it is reasonable that a concrete class may implement more than one interface.

Corresponding Ada rule:

Ada only allows inheritance from one base class. Ada allows multiple inheritance of interfaces.

11.           Member access control

11.1.                 Access specifiers

11.1.1.                        Declare all data member private

If direct access to the object state is allowed through public or protected member data, encapsulation is reduced making the code harder to maintain.
By implementing a class interface with member functions only, precise control is achieved over modifications to object state as well as allowing for pre and post conditions to be checked when accessing data.

Corresponding Ada rule:

There are many uses for packages. One use is to define commonly used constants, while another is to define abstract data types. It is good to use data hiding for abstract data types, but it is also important to openly share commonly used constants. When declaring abstract data types in Ada it is advised that the public view of the data type refers to a private definition of the data structure.

11.2.                 Friends

11.2.1.                        Do not use friend declarations

Friend declarations reduce encapsulation, resulting in code that is harder to maintain.

Corresponding Ada rule:

Ada provides child packages rather than friends. Child packages do not reduce encapsulation. Child packages allow the creation of package extensions without compromising or altering the code in the parent package.

12.           Special member functions

12.1.                 Conversions

12.1.1.                        Do not declare implicit user defined conversions

A user defined conversions can occur through the use of a conversion operator or a conversion constructor (a constructor that accepts a single argument).
A compiler can invoke a single user defined conversion in a standard conversion sequence, but only if the operator or constructor is declared without the explicit keyword.
It is better to declare all conversion constructors and operators explicit.

Corresponding Ada rule:

Ada does not perform implicit conversions.

12.2.                 Destructors

12.2.1.                        Declare virtual, private, or protected the destructor of a type used as a base class

If an object will ever be destroyed through a pointer to its base class, then the destructor in the base class should be virtual. If the base class destructor is not virtual, then the destructors for derived classes will not be invoked.
Where an object will not be deleted via a pointer to its base, then the destructor should be declared with protected or private access. This will result in a compile error should an attempt be made to delete the object incorrectly.

Corresponding Ada rule:

Ada controlled types allow user-defined finalization of objects of the type. Finalization is called when an object goes out of scope.
Ada polymorphism does not require the use of pointers to a base class.

12.3.                 Free store

12.3.1.                        Correctly declare overloads for operator new and delete

operator new and operator delete should work together. Overloading operator new means that a custom memory management scheme is in operation for a particular class or program. If a corresponding operator delete (plain or array) is not provided the memory management scheme is incomplete.
Additionally, if initialization of the allocated object fails with an exception, the C++ runtime will try to call an operator delete with identical parameters as the called operator new, except for the first parameter. If no such operator delete can be found, the memory will not be freed. If this operator delete does not actually need to perform any bookkeeping, one with an empty body should be defined to document this in the code.
When declared in a class, operator new and operator delete are implicitly static members; explicitly including the static specifier in their declarations helps to document this.

Corresponding Ada rule:

When creating a custom storage pool one must override the abstract type Root_Storage_Pool, including overriding its Allocate and Deallocate procedures and its Storage_Size function. There is no way to implement a custom storage pool without implementing all three subprograms.

12.4.                 Initializing bases and members

12.4.1.                        Do not use the dynamic type of an object unless the object is fully constructed

Expressions involving:
·         a call to a virtual member function,
·         use of typeid, or
·         a cast to a derived type using dynamic cast
are said to use the dynamic type of the object.
Special semantics apply when using the dynamic type of an object while it is being constructed or destructed. Moreover, it is undefined behavior if the static type of the operand is not (or is not a pointer to) the constructor’s or destructor’s class or one of its base classes.
In order to avoid misconceptions and potential undefined behavior, such expressions should not be used while the object is being constructed or destructed.

Corresponding Ada rule:

Ada has no concept of a dynamic type, only dynamic type identification. Ada can perform view conversions of a child type to its parent type, but that does not change the type of an object.

12.4.2.                        Ensure that a constructor initializes explicitly all base classes and non-static data members

A constructor should completely initialize its object. Explicit initialization reduces the risk of an invalid state after successful construction. All virtual base classes and direct non-virtual base classes should be included in the initialization list for the constructor. A copy or move constructor should initialize each non-static data member in the initialization list, or if this is not possible then in constructor body. For other constructors, each non-static data member should be initialized in the following way, in order of preference:
·         non static data member initializer (NSDMI), or
·         in initialization list, or
·         in constructor body.
For many constructors this means that the body becomes an empty block.

Corresponding Ada rule:

Ada records, including tagged records, can be defined with default values for all their record components. The default initialization of a parent tagged record is applied to the child tagged record so that the child need not explicitly call parent initialization functions.

12.4.3.                        Do not specify both an NSDMI and a member initialize for the same non-static member

NSDMI stands for ’non static data member initializer’. This syntax, introduced in the 2011 C++ Language Standard, allows for the initializer of a member to be specified along with the declaration of the member in the class body. To avoid confusion as to the value of the initializer actually used, if a member has an NSDMI then it should not subsequently be initialized in the member initialization list of a constructor.

Corresponding Ada rule:

Ada does not provide for multiple initialization of an object.

12.4.4.                        Write members in an initialization list in the order in which they are declared

Regardless of the order of member initializers in a initialization list, the order of initialization is always:
·         Virtual base classes in depth and left to right order of the inheritance graph.
·         Direct non-virtual base classes in left to right order of inheritance list.
·         Non-static member data in order of declaration in the class definition.
To avoid confusion and possible use of uninitialized data members, it is recommended that the initialization list matches the actual initialization order.

Corresponding Ada rule:

Since Ada only allows single inheritance from a base class there is no issue about which base class is first initialized.

12.4.5.                        Use delegating constructors to reduce code duplication

Delegating constructors can help reduce code duplication by performing initialization in a single constructor. Using delegating constructors also removes a potential performance penalty with using an ’init’ method, where initialization for some members occurs twice.

Corresponding Ada rule:

Ada does not provide explicit constructors. Ada does provide an Initialize procedure for controlled types.

12.5.                 Copying and moving class objects

12.5.1.                        Define explicitly =default or =delete implicit special member functions of concrete classes

A compiler may provide some or all of the following special member functions:
·         Destructor
·         Copy constructor
·         Copy assignment operator
·         Move constructor
·         Move assignment operator
The set of functions implicitly provided depends on the special member functions that have been declared by the user and also the special members of base classes and member objects.
The compiler generated versions of these functions perform a bitwise or shallow copy, which may not be the correct copy semantics for the class. It is also not clear to clients of the class if these functions can be used or not.
To resolve this, the functions should be defined with =delete or =default thereby fully documenting the class interface.
Note: As this rule is limited to concrete classes, it is the responsibility of the most derived class to ensure that the object has correct copy semantics for itself and for its sub-objects.

Corresponding Ada rule:

Tagged types inheriting from Controlled types must define Initialize, Adjust, and Finalize procedures to handle initialization, copy semantics, and deletion semantics.

12.5.2.                        Define special members =default if the behavior is equivalent

Corresponding Ada rule:

This rule has no Ada equivalent because Ada prohibits inheritance from multiple base classes.

12.5.3.                        Ensure that a user defined move/copy constructor only moves/copies base and member objects

The human clients of a class will expect that the copy constructor can be used to correctly copy an object of class type. Similarly, they will expect that the move constructor correctly moves an object of class type.
Similarly, a compiler has explicit permission in the C++ Standard to remove unnecessary copies or moves, on the basis that these functions have no other side-effects other than to copy or move all bases and members.

Corresponding Ada rule:

Tagged types inheriting from Controlled types must define Initialize, Adjust, and Finalize procedures to handle initialization, copy semantics, and deletion semantics.

12.5.4.                        Declare noexcept the move constructor and move assignment operator

A class provides the Strong Exception Guarantee if after an exception occurs, the objects maintain their original values.
The move members of a class explicitly change the state of their argument. Should an exception be thrown after some members have been moved, then the Strong Exception Guarantee may no longer hold as the from object has been modified.
It is especially important to use noexcept for types that are intended to be used with the standard library containers.
If the move constructor for an element type in a container is not noexcept then the container will use the copy constructor rather than the move constructor.

Corresponding Ada rule:

Ada has no equivalent to a move constructor. A move constructor moves ownership of data from one object to another. If the resource is accessed through an access value then the access value of the data in the starting object must be copied to the corresponding access field of the target object. After the access value is copied the access value in the starting object must be set to null.

12.5.5.                        Correctly reset moved-from handles to resources in the move constructor

The move constructor moves the ownership of data from one object to another. Once a resource has been moved to a new object, it is important that the moved-from object has its handles set to a default value. This will ensure that the moved-from object will not attempt to destroy resources that it no longer manages on its destruction.
The most common example of this is to assign nullptr to pointer members.

Corresponding Ada rule:

Ada has no equivalent to a move constructor. A move constructor moves ownership of data from one object to another. If the resource is accessed through an access value then the access value of the data in the starting object must be copied to the corresponding access field of the target object. After the access value is copied the access value in the starting object must be set to null.

12.5.6.                        Use and atomic, non-throwing swap operation to implement the copy and move assignment operators

Implementing the copy assignment operator using a non throwing swap provides the Strong Exception Guarantee for the operations.
In addition, the implementation of each assignment operator is simplified without requiring a check for assignment to self.

Corresponding Ada rule:

Use of an atomic swap operation assumes the use of pointers and not full objects. This assumption frequently fails since Ada does not require the use of pointers to complex data types.

12.5.7.                        Declare assignment operators with the ref-qualifier &

In the 2003 C++ Language Standard, user declared types differed from built-in types in that it was possible to have a ’modifiable rvalue’.
The 2011 C++ Language Standard allows for a function to be declared with a reference qualifier. Adding & to the function declaration ensures that the call can only be made on lvalue objects, as is the case for the built-in operators.

Corresponding Ada rule:

Ada does not allow modifiable rvalues.

12.5.8.                        Make the copy assignment operator of an abstract class protected ore define it =delete

An instance of an abstract class can only exist as a subobject for a derived type. A public copy assignment operator would allow for incorrect partial assignments to occur.
The copy assignment operator should be protected, or alternatively defined =delete if copying is to be prohibited in this class hierarchy.

Corresponding Ada rule:

Ada does not allow instances of abstract types. Each instance must be a concrete type derived from the abstract type.

13.           Overloading

13.1.                 Overload resolution

13.1.1.                        Ensure that all overloads of functions are visible from where it is called

When a member function is overridden or overloaded in a derived class, other base class functions of that name will be hidden. A call to a function from the derived class may therefore result in a different function being called than if the same call had taken place from the base class.
To avoid this situation, hidden names should be introduced into the derived class through a using declaration.
A using declaration for a namespace scope identifier, only brings into the current scope the prior declarations of this identifier, and not any declarations subsequently added to the namespace. This too may lead to unexpected results for calls to overloaded functions.

Corresponding Ada rule:

If the programmer wants the base type version of an overloaded or overridden subprogram to be called then the object must be passed to the overloaded subprogram name in the form of a view conversion to the base type.

13.1.2.                        If a member of a set of callable functions includes a universal reference parameter, ensure that one appears in the same position for all other members

A callable function is one which can be called with the supplied arguments. In the C++ Language Standard, this is known as the set of viable functions.
A template parameter declared T&& has special rules during type deduction depending on the value category of the argument to the function call. Scott Meyers has named this a ’Universal Reference’.
As a universal reference will deduce perfectly for any type, overloading them can easily lead to confusion as to which function has been selected.
Exception:

Standard C++ allows for a member of the viable function set to be deleted. In such cases, should these functions be called then it will result in a compiler error.

Corresponding Ada rule:

Ada does not provide an equivalent to universal reference. The closest Ada comes is an class-wide access type, which can reference any object in the inheritance hierarchy rooted at the tagged type specified in the definition of the access type.

13.2.                 Overloaded operators

13.2.1.                        Do not overload operators with special semantics

Overloaded operators are just functions, so the order of evaluation of their arguments is unspecified. This is contrary to the special semantics of the following built-in operators:
·         && – left to right and potentially evaluated
·          || – left to right and potentially evaluated
·          , – left to right
Providing user declared versions of these operators may lead to code that has unexpected behavior and is therefore harder to maintain.
Additionally, overloading the unary & (address of) operator will result in undefined behavior if the operator is used from a location in the source where the user provided overload is not visible.

Corresponding Ada rule:

The C++ operators && and || correspond to the Ada logical expressions “and then” and “or else”. Those logical expressions cannot be overloaded in Ada. Ada has no “address of” operator. It does have an attribute ‘Access which cannot be overloaded.

13.2.2.                        Ensure that the return type of an overloaded binary operator matches the built-in counterparts

Built-in binary arithmetic and bitwise operators return a pure rvalue (which cannot be modified), this should be mirrored by the overloaded versions of these operators. For this reason the only acceptable return type is a fundamental or an enumerated type or a class type with a reference qualified assignment operator.
Built-in equality and relational operators return a boolean value, and so should the overloaded counterparts.

Corresponding Ada rule:

Custom operators can be defined which return complex values. For instance:
type Matrix is array(Natural range 1..10, Natural range 1..10) of float;

type Vector is array(Natural range 1..10) of float;

function “+” (Left, Right : Matrix) return Matrix;

function “+” (Left, Right : Vector) return Vector;

In the case above addition operators are declared for types Matrix and Vector. In this case the acceptable return type is not a scalar and need not be a tagged type.

13.2.3.                        Declare binary arithmetic and bitwise operators as non-members

Overloaded binary arithmetic and bitwise operators should be non-members to allow for operands of different types, e.g. a fundamental type and a class type, or two unrelated class types.

Corresponding Ada rule:

Overloaded binary arithmetic operators should be declared within the package that a particular numeric type is declared. Integer or real types are often defined as part of a larger abstraction such as date and time utilities. Often achieving the same effect in C++ may take the creation of several classes.

13.2.4.                        When overloading the subscript operator (operator[]) implement both const and non-const versions

A non-const overload of the subscript operator should allow an object to be modified, i.e. should return a reference to member data. The const version is there to allow the operator to be invoked on a const object.

Corresponding Ada rule:

There is no subscript operator in Ada. Ada requires the programmer to specify the scalar subtype used to index an array type. Array indices in Ada may be indexed by any scalar type (signed integer, modular integer, enumeration) and the lowest index value may be set to any valid value of the specified index subtype.
Examples:
Type Days is (Mon, Tues, Wed, Thu, Fri, Sat, Sun);

Type Weekly_Sales is array(Days) of float;

This array’s index values start at Mon and end at Sun. Each array element is a float value.

Type Mod_Index is mod 10;

Type Circular_Array is array(Mod_Index) of message;

This array is indexed with a modular type. Modular types exhibit wrap around arithmetic, allowing this array to easily implement a circular message buffer.

Subtype Normalized_Index is Integer range -100..100;

Type Normal_Distribution is array(Normalized_Index) of Count;

This array can be used to count the frequency of some event plotted to a normal curve.
Ada does allow indexing operators to be defined for tagged types. This indexing ability in Ada helps greatly in the implementation of indexable container types such as maps and sets. The container type can be designated to employ either constant indexing or variable indexing. The aspect of the type declaring either constant indexing or variable indexing must indicate one or more functions taking two parameters, one of which is an instance of the container type or a reference to a container instance.
Ada user defined indexing is not used for array types, which are not tagged types.

13.2.5.                        Implement a minimal set of operators and use them to implement all other related operators

In order to limit duplication of code and associated maintenance overheads, certain operators can be implemented in terms of other operators.

Corresponding Ada rule:

The Ada 2012 Language Reference Manual states that one is not allowed to overload “/=” directly, however overloading “=” implicitly overloads “/=”, since not equals is simply the complement of the “=” function.

14.           Templates

14.1.                 Template declarations

14.1.1.                        Using variadic templates rather than ellipsis

Use of the ellipsis notation ... to indicate an unspecified number of arguments should be avoided. Variadic templates offer a type-safe alternative.

Corresponding Ada rule:

The Ada equivalent to a template is called a generic. A programmer can define a generic subprogram or a generic package. Each generic unit definition includes a set of generic parameters followed by a normal subprogram or package specification.
There is no need for an equivalent to C++ variadic parameter lists.
Generic units are explained in section 12 of the Ada 2012 Language Reference Manual.
Generic parameters can be:
·         Formal objects
·         Formal private types and derived types
·         Formal scalar types
·         Formal array types
·         Formal access types
·         Formal subprograms
·         Formal packages
Example of a generic package:
generic
   Size : Positive;
   type Item is private;
package Stack is
   procedure Push(E : in  Item);
   procedure Pop (E : out Item);
   Overflow, Underflow : exception;
end Stack;

package body Stack is

   type Table is array (Positive range <>) of Item;
   Space : Table(1 .. Size);
   Index : Natural := 0;

   procedure Push(E : in Item) is
   begin
      if Index >= Size then
         raise Overflow;
      end if;
      Index := Index + 1;
      Space(Index) := E;
   end Push;

   procedure Pop(E : out Item) is
   begin
      if Index = 0 then
         raise Underflow;
      end if;
      E := Space(Index);
      Index := Index - 1;
   end Pop;

end Stack;
This example implements a generic stack. The package specification begins with  the reserved word “generic” followed by the list of generic parameters. In this case the parameter Size is a generic formal object of the subtype Positive, and the type Item is any non-limited type, private or public.
The package specification only contains two procedures, Push and Pop, plus the definition of two exceptions, Overflow and Underflow. This package implements a singleton stack, therefore no stack type is publicly exposed.
The package body contains the implementation of the Push and Pop procedures plus the definition of the singleton stack object.

14.2.                 Template instantiation and specialization

14.2.1.                        Declare template specializations in the same file as the primary template they specialize

Partial and explicit specializations of function and class templates should be declared with the primary template.
This will ensure that implicit specializations will only occur when there is no explicit declaration available.

Corresponding Ada rule:

Ada generics do not undergo specialization, only instantiation. Examples of instantiation of the generic singleton stack package shown above are:
Package Stack_Int is new Stack(Size => 200, Item => Integer);

Package Stack_Bool is new Stack(100, Boolean);

Note that the generic parameters can be passed by name or by position. After the instantiations above the package procedures can be called as follows:
Stack_Int.Push(N);

Stack_Bool.Push(True);

Note that Stack_Int is a singleton stack containing Integer values while Stack_Bool is a distinct and separate singleton stack containing Boolean values.

14.2.2.                        Do not explicitly specialize a function template that is overloaded with other templates

Overload resolution does not take into account explicit specializations of function templates. Only after overload resolution has chosen a function template will any explicit specializations be considered.

Corresponding Ada rule:

Ada generic formal parameters are much richer and more explicit than C++ template parameters rendering specialization unnecessary.
Use of a formal package parameter in a generic parameter list does not necessarily cause overloading of subprograms.

14.2.3.                        Declare extern an explicitly instantiated template

Declaring the template with extern will disable implicit instantiation of the template when it is used in other translation units, saving time and reducing compile time dependencies.

Corresponding Ada rule:

Ada generics are never implicitly instantiated.

15.           Exception handling

15.1.                 Throwing an exception

15.1.1.                        Only use instances of std::exception for exceptions

Exceptions pass information up the call stack to a point where error handling can be performed. If an object of class type is thrown, the class type itself serves to document the cause of an exception.
Only types that inherit from std::exception, should be thrown.

Corresponding Ada rule:

Only instances of the pre-defined type Exception can be raised. Each instance of exception that is raised can be accompanied by exception information in the form of a string.

15.2.                 Constructors and destructors

15.2.1.                        Do not throw an exception from a destructor

The 2011 C++ Language Standard states that unless a user provided destructor has an explicit exception specification, one will be added implicitly, matching the one that an implicit destructor for the type would have received.
Furthermore when an exception is thrown, stack unwinding will call the destructors of all objects with automatic storage duration still in scope up to the location where the exception is eventually caught.
The program will immediately terminate should another exception be thrown from a destructor of one of these objects.

Corresponding Ada rule:

Ada has no explicit constructors or destructors. Instead, the procedures Initialize and Finalize are called. When a programmer customizes Initialize and Finalize care must be taken not to raise exceptions.

15.3.                 Handling an exception

15.3.1.                        Do not access non-static members from a catch handler of constructor/destructor function try block

When a constructor or a destructor has a function try block, accessing a non-static member from an associated exception handler will result in undefined behavior.

Corresponding Ada rule:

The response to one or more exceptions is specified by an exception_handler.

Syntax

handled_sequence_of_statements ::= 
     
sequence_of_statements
  [exception
     
exception_handler
    {
exception_handler}]

exception_handler ::= 
  when [
choice_parameter_specification:] exception_choice {| exception_choice} =>
     
sequence_of_statements

choice_parameter_specification ::= defining_identifier

exception_choice ::= exception_name | others

Example of an exception handler: 

begin
   Open(File, In_File, "input.txt"); 
exception
   when E : Name_Error =>
      Put("Cannot open input file : ");
      Put_Line(Exception_Message(E));
      raise;
end;

15.3.2.                        Ensure that a program does not result in a call to std::terminate

The path of an exception should be logical and well defined. Throwing an exception that is never subsequently caught, or attempting to rethrow when an exception is not being handled is an indicator of a problem with the design.

Corresponding Ada rule:

Ada exceptions should be handled wherever possible. It is considered bad design to use exceptions as a normal path to program termination.

16.           Preprocessing

16.1.                 Source file inclusion

16.1.1.                        Use the preprocessor only for implementing include guards, and including header files with include guards

The preprocessor should only be used for including header files into other headers or the main source file, in order to form a translation unit. In particular only the following include directive forms should be used:
·         #include <xyz>

·         #include "xyz"

Corresponding Ada rule:

Ada does not require the use of a preprocessor. File dependencies are established through the use of a dependency clause (with clause).
Dependency clauses do not require any equivalent of include guards. Dependency clauses cannot create macros.

16.1.2.                        Do not include a path specifier in filenames supplied in #include directives

Hardcoding the path to a header file in a #include directive may necessitate changes to source code when it is reused or ported.
Alternatively, the directory containing the header file should be passed to the compiler on command line (e.g. –I or /i option).

Corresponding Ada rule:

Ada dependency clauses do not contain filenames. They contain the names of compilation units.

16.1.3.                        Match the filename in a #include directive to the one on the filesystem

Some operating systems have case insensitive filesystems. Code initially developed on such a system may not compile successfully when ported to a case sensitive filesystem.

Corresponding Ada rule:

Ada dependency clauses do not contain filenames. They contain the names of compilation units.

16.1.4.                        Use <> brackets for system and standard library headers. Use quotes for all other headers.

It is common practice that #include <...> is used for compiler provided headers, and #include "..." for user provided files.
Adhering to this guideline therefore helps with the understandability and maintainability of the code.

Corresponding Ada rule:

Ada dependency clauses do not contain filenames. They contain the names of compilation units.

16.1.5.                        Include directly the minimum number of headers required for compilation

Presence of spurious include directives can considerably slow down compilation of a large code base. When a source file is refactored, the list of included headers should be reviewed, to remove include directives which are no longer needed.
Doing so may also offer an opportunity to delete from code repository source files that are no longer used in the project, therefore reducing the level of technical debt.

Corresponding Ada rule:

It is a good practice to minimize the dependency list for an Ada compilation unit. Minimized dependencies support code readability and maintenance.

17.           Standard library

17.1.                 General

17.1.1.                        Do not use std::vector<bool>

The std::vector<bool> specialization does not conform to the requirements of a container and does not work as expected in all STL algorithms.
In particular &v[0] does not return a contiguous array of elements as it does for other vector types. Additionally, the C++ Language Standard guarantees that different elements of an STL container can safely be modified concurrently, except for a container of std::vector<bool> type.

Corresponding Ada rule:

There is no corresponding prohibition for Ada standard libraries.

17.2.                 The C standard library

17.2.1.                        Wrap use of the C Standard Library

The C11 standard library, which is included in the C++ standard library, leaves the handling of concerns relating to security and concurrency up to the developer.
Therefore, if the C standard library is to be used, it should be wrapped, with the wrappers ensuring that undefined behavior and data races will not occur.

Corresponding Ada rule:

The C standard library is not included in the Ada 2012 standard library.

17.3.                 General utilities library

17.3.1.                        Do not use std::move on objects declared with const or const & type

An object with const or const & type will never actually be moved as a result of calling std::move.

Corresponding Ada rule:

There is no operation equivalent to C++ std::move defined in the Ada 2012 standard.

17.3.2.                        Use std::forward to forward universal references

The std::forward function takes the value category of universal reference parameters into account when passing arguments through to callees.
When passing a non universal reference argument std::move should be used.
Note: As auto is implemented with argument deduction rules, an object declared with auto && is also a universal reference for the purposes of this rule.

Corresponding Ada rule:

There are no Ada library components needed to pass parameter values to subprograms.

17.3.3.                        Do not subsequently use the argument to std::forward

Depending on the value category of arguments used in the call of the function, std::forward may or may not result in a move of the parameter.
When the value category of the parameter is an lvalue, then modifications to the parameter will affect the argument of the caller. In the case of an rvalue, the value should be considered as being indeterminate after the call to std::forward (See Rule 8.4.1: ”Do not access an invalid object or an object with indeterminate value”).

Corresponding Ada rule:

There are no Ada library components needed to pass parameter values to subprograms.

17.3.4.                        Do not create smart pointers of an array type

Memory allocated with array new must be deallocated with array delete. A smart pointer that refers to an array object must have this information passed in when the object is created. A consequence of this is that it is not possible to construct such a smart pointer using std::make shared.
A std::array or std::vector can be used in place of the raw array type. The usage and performance will be very similar but will not have the additional complexity required when deallocating the array object.

Corresponding Ada rule:

There is not special complexity dealing with allocating or deallocating arrays in Ada.

17.3.5.                        Do not create an rvalue reference of std::array

The std::array class is a wrapper for a C style array. The cost of moving std::array is linear with each element of the array being moved. In most cases, passing the array by & or const & will provide the required semantics without this cost.

Corresponding Ada rule:

There is no standard wrapper for Ada arrays. The cost of assigning an array or an array slice is equivalent to a memory copy in Ada.

17.4.                 Containers library

17.4.1.                        Use const container calls when result is immediately converted to a const iterator

The 2011 C++ Language Standard introduced named accessors for returning const iterators. Using these members removes an implicit conversion from iterator to const iterator.
Another benefit is that the declaration of the iterator object can then be changed to use auto without the danger of affecting program semantics.

Corresponding Ada rule:

Ada container packages define cursors to traverse a container objects. The cursor type is a private type defined in each standard container package.

17.4.2.                        Use API calls that construct objects in place

The 2011 C++ Language Standard allows for perfect forwarding. This allows for the arguments to a constructor to be passed through an API and therefore allowing for the final object to be constructed directly where it is intended to be used.

Corresponding Ada rule:

Parameter passing needs no special passing helpers.

17.5.                 Algorithms library

17.5.1.                        Do not ignore the result of std::remove, std::remove_if or std::unique

The mutating algorithms std::remove, std::remove if and both overloads of std::unique operate by swapping or moving elements of the range they are operating over.
On completion, they return an iterator to the last valid element. In the majority of cases the correct behavior is to use this result as the first operand in a call to std::erase.

Corresponding Ada rule:

While Ada does not provide an equivalent to these functions as separate library components, the concept of ignoring the return value of a function is foreign to Ada. All function return values must be used as an rvalue to some expression.

18.           Concurrency

18.1.                 General

18.1.1.                        Do not use platform specific multi-threading facilities

Rather than using platform-specific facilities, the C++ standard library should be used as it is platform independent.

Corresponding Ada rule:

Ada tasking is platform independent. There are no platform specific multi-threading facilities written with an Ada API.

18.2.                 Threads

18.2.1.                        Use high_intergrity::thread in place of std::thread

The destructor of std::thread will call std::terminate if the thread owned by the class is still joinable. By using a wrapper class a default behavior can be provided.

Corresponding Ada rule:

Ada tasks do not have an explicit join command nor an explicit detach command.
Ada tasks do have a dependency hierarchy. Each task (other than the environment task) depends on one or more masters. A task is said to be completed when the execution of the corresponding task body is completed. A task is said to be terminated when any finalization of the task body has been performed. The first step in finalizing a master is to wait for the termination of any tasks dependent upon the master. The task executing the master is blocked until all dependents have terminated. Any remaining finalization is then performed and the master is left.

18.2.2.                        Synchronize access to data shared between threads using a single lock

Using the same lock when accessing shared data makes it easier to verify the absence of problematic race conditions.
To help achieve this goal, access to data should be encapsulated such that it is not possible to read or write to the variable without acquiring the appropriate lock. This will also help limit the amount of code executed in the scope of the lock.
Note: Data may be referenced by more than one variable, therefore this requirement applies to the complete set of variables that could refer to the data.
Special attention needs to be made for const objects. The standard library expects operations on const objects to be thread-safe. Failing to ensure that this expectation is fulfilled may lead to problematic data races and undefined behavior. Therefore, operations on const objects of user defined types should consist of either reads entirely or internally synchronized writes.

Corresponding Ada rule:

Ada provides two forms of synchronization for passing data between tasks.
The Rendezvous mechanism provides a means to synchronously pass data directly between two tasks.
The Protected Object provides a way to pass data between tasks through a shared buffer. Protected objects are allowed to have a combination of three kinds of methods.
Protected procedures allow data in the protected object to be modified or updated unconditionally. Protected procedures implicitly manipulate a read/write lock on the protected object. Protected entries allow data in the protected object to be modified or updated conditionally.
 Protected entries have a boundary condition which must be satisfied. When the boundary condition evaluates to False the protected entry is blocked and the calling task is suspended and placed in an entry queue. Protected entries automatically manipulate a read/write lock on the protected object. Protected functions are only allowed read access to the protected object. Protected functions may not modify or update the state of the protected object. Protected functions automatically manipulate a shared read lock on the protected object allowing multiple tasks to read from the protected object simultaneously.

18.2.3.                        Do not share volatile data between threads

Declaring a variable with the volatile keyword does not provide any of the required synchronization guarantees:
·         Atomicity
·         Visibility
·          Ordering
Use mutex locks or ordered atomic variables, to safely communicate between threads and to prevent the compiler from optimizing the code incorrectly.

Corresponding Ada rule:

Use the Rendevous or protected objects to communicate between tasks.

18.2.4.                        Use the std::call_once rather than the Double_Checked Locking pattern

The Double-Checked Locking pattern can be used to correctly synchronize initializations.
However, the C++ standard library provides std::call_once which allow for a cleaner implementation.
Initialization of a local object with static storage duration is guaranteed by the C++ Language Standard to be reentrant.
However this conflicts with Rule 3.3.1: ”Do not use variables with static storage duration”, which takes precedence.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.3.                 Mutual exclusion

18.3.1.                        Within the scope of a lock, ensure that no static path results in a lock of the same mutex

It is undefined behavior if a thread tries to lock a std::mutex it already owns, this should therefore be avoided.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.3.2.                        Ensure that order of nesting of locks in a project forms a DAG

Mutex locks are a common causes of deadlocks. Multiple threads trying to acquire the same lock but in a different order may end up blocking each other.
When each lock operation is treated as a vertex, two consecutive vertices with no intervening lock operation in the source code are considered to be connected by a directed edge. The resulting graph should have no cycles, i.e. it should be a Directed Acyclic Graph (DAG).

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.3.3.                        Do not use stfd::recursive_mutex

Use of std::recursive mutex is indicative of bad design: Some functionality is expecting the state to be consistent which may not be a correct assumption since the mutex protecting a resource is already locked.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.3.4.                        Only use std::unique_lock when std::lock_guard cannot be used

The std::unique lock type provides additional features not available in std::lock guard. There is an additional cost when using std::unique lock and so it should only be used if the additional functionality is required.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.3.5.                        Do not use the members of std::mutex directly

A mutex object should only be managed by the std::lock guard or std::unique lock object that owns it.

18.3.6.                        Do not use relaxed atomics

Using non-sequentially consistent memory ordering for atomics allows the CPU to reorder memory operations resulting in a lack of total ordering of events across threads. This makes it extremely difficult to reason about the correctness of the code.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.4.                 Condition variables

18.4.1.                        Do not use std::condition_variable_any on a std::mutex

When using std::condition variable any, there is potential for additional costs in terms of size, performance or operating system resources, because it is more general than std::condition variable.
std::condition variable works with std::unique lock<std::mutex>, while std::condition variable any can operate on any objects that have lock and unlock member functions.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.


  • 22 March 2017 at 20:31

An Ada Publish-Subscribe Producer-Consumer Exercise

There are many ways to express the classic producer-consumer problem in Ada. Following is a problem which illustrates some of the interesting features of Ada protected objects.

Problem Statement

This producer-consumer problem has several requirements:
·         There shall be one producer and many consumers.
·         The number of consumers shall be established at run-time through user input.
·         The producer shall produce a series of random floating point numbers
·         All consumers shall read each number produced by the producer exactly once.
·         The producer shall not know the number of consumers
The requirement that the producer shall not know the number of consumers prohibits the use of the Ada Rendezvous mechanism for direct communication between the producer and all the consumers. An alternative compliant with this requirement is to use a protected object as a shared buffer between the producer and all the consumers. In fact, the solution below uses a protected object that implements a publish-subscribe mechanism for communication between the producer and all the consumers.

A Publish-Subscribe Solution

The example below handles the publish-subscribe details through the protected object. Each consumer must register with the protected object before it can read data from the protected object. The protected object only allows the producer to publish data when all the registered consumers are ready to read the data. The registered consumers  are only allowed to read a published value once.
The package specification for the Ada tasks and protected object is:



---------------------------------------------------------------------

-- This package implements a consumer task type and its associated

-- protected object.

-- All instances of the consumer task type consume data from the

-- protected object once each time it is written by the producer.

-- The producer cannot write a new value until all consumers are

-- ready to read the next value.

-- The producer does not know how many consumers there are, but must

-- not write any values until there is at least one consumer.

---------------------------------------------------------------------

with Ada.Task_Identification; use Ada.Task_Identification;


package Batch_Consumers is

   task type Consumer is

      entry Stop;

   end Consumer;

  

   task Producer is

      entry Stop;

   end Producer;


   protected Buffer is

      procedure Register;

      entry Read  (Value : out Float);

      entry Write (Value : in Float);

   private

      The_Value       : Float   := Float'First;

      Done            : Boolean := False;

      Trigger         : Natural := 0;

   end Buffer;


end Batch_Consumers;

The task type Consumer allows multiple consumers to be defined, while the task object Producer is unique, as is the protected object Buffer.
Both tasks have a Stop entry to facilitate shutting down the program in an orderly manner.
The instances of the task type Consumer will Register once before reading data from Buffer. The Consumers will then read data from Buffer until they are told to Stop.
The task Producer will write random floating point numbers to Buffer until Producer is told to Stop.
The devil is in the details, and the details are expressed in the package body.



with Ada.Task_Identification; use Ada.Task_Identification;

with Ada.Text_IO; use Ada.Text_IO;

with Ada.Numerics.Float_Random; use Ada.Numerics.Float_Random;


package body Batch_Consumers is


   --------------

   -- Consumer --

   --------------


   task body Consumer is

      My_Value : Float := Float'First;

   begin

      Buffer.Register;

      loop

         select

            accept Stop;

            exit;

         else

            select

               Buffer.Read(My_Value);

               Put_Line("Task " & Image(Current_Task) & " read the value: " &

                          Float'Image(My_Value));

            or

               delay 0.01;

            end select;

         end select;

      end loop;

   end Consumer;

  

   --------------

   -- Producer --

   --------------

  

   task body Producer is

      Num  : Float;

      Seed : Generator;

   begin

      Reset(Seed);

      loop

         select

            accept Stop;

            exit;

         else

            Num := Random(Seed);

            Put_Line("********************** Writing " & Float'Image(Num));

            Buffer.Write(Num);

         end select;

      end loop;

   end Producer;

  

   ------------

   -- Buffer --

   ------------


   protected body Buffer is


      --------------

      -- Register --

      --------------


      procedure Register is

      begin

         Trigger := Trigger + 1;

      end Register;


      ----------

      -- Read --

      ----------


      entry Read (Value : out Float) when Done is

      begin

         Value := The_Value;

         if Read'Count = 0 then

            Done := False;

         end if;

      end Read;


      -----------

      -- Write --

      -----------


      entry Write (Value : in Float) when Trigger > 0 and then

        Read'Count >= Trigger and then not Done is

      begin

         The_Value := Value;

         Done      := True;

      end Write;


   end Buffer;


end Batch_Consumers;


All the interesting control elements are embedded into the guard conditions for the protected entries Read and Write.
Every Ada entry maintains its own entry Queue. Tasks are enqueued, and suspended while the guard condition is false. Tasks are dequeued while the guard condition is true. Ada entry queues follow the Internal Progress First Rule, which means that all tasks queued on an entry call will be serviced, once the entry guard is true, before any other calls are accepted.
This rule is used to make the Read entry work as we want. When Done evaluates to true all tasks in the Read entry queue are serviced before any more calls on Read are accepted. Thus, within the body of the Read entry the value Done is set to False when the entry queue is empty. This closes the door to any more reads until the Write entry executes.
The Write entry will only execute under the following conditions:
·         There is at least one consumer registered and
·         The number of tasks waiting in the Read queue is at least as large as the number of registered consumers and
·         Done is False.
You can see here that the Buffer is aware of the number of consumers, but the Producer is not.
The main procedure used to test this package is:
with Batch_Consumers;     use Batch_Consumers;

with Ada.Text_IO;         use Ada.Text_IO;

with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;


procedure Batch_Test is

   Num_Consumers : Positive;

begin

   Put ("Enter the number of consumer tasks: ");

   Get (Num_Consumers);

   declare

      Consumers : array (1 .. Num_Consumers) of Consumer;

   begin

      delay 2.0; -- wait for 2 seconds

      Producer.Stop;

      for C of Consumers loop

         C.Stop;

      end loop;

   end;

end Batch_Test;



The main procedure prompts the user for the number of consumer tasks, reads the response, then, in an inner block, declares an array of consumer equal to the number of consumer specified by the user. The main procedure delays for 2.0 seconds then stops Producer and each Consumer.
  • 1 July 2017 at 23:55

Incompetent C Programming Example

I have been bothered by the C coding samples I commonly find on the Web. Almost all of them contain grievous defects. The following example of an array-based stack using C is based upon C code published at http://www-cs.ccny.cuny.edu/~peter/dstest.html

The C code published at http://www-cs.ccny.cuny.edu/~peter/dstest.html supports the book Advanced Data Structures authored by Peter Braß and published by Cambridge University Press.

Array Stack

The C code for Array Stack is shown below.

Example 1 ArrayStack.c
#include <stdio.h>

#include <stdlib.h>


typedef int item_t;


typedef struct {item_t *base; item_t *top; int size;} stack_t;


stack_t *create_stack(int size)

{   stack_t *st;

    st = (stack_t *) malloc( sizeof(stack_t) );

    st->base = (item_t *) malloc( size * sizeof(item_t) );

    st->size = size;

    st->top = st->base;

    return( st );

}


int stack_empty(stack_t *st)

{   return( st->base == st->top );

}


int push( item_t x, stack_t *st)

{   if ( st->top < st->base + st->size )

    {   *(st->top) = x; st->top += 1;  return( 0 );

    }

    else

       return( -1 );

}


item_t pop(stack_t *st)

{   st->top -= 1;

    return( *(st->top) );

}


item_t top_element(stack_t *st)

{   return( *(st->top -1) );

}


void remove_stack(stack_t *st)

{   free( st->base );

    free( st );

}

int main()

{  stack_t *st;

   char nextop;

   st = create_stack(50);

   printf("Made Array-Based Stack of size 50\n");

   while( (nextop = getchar())!= 'q' )

   { if( nextop == 'i' )

     { int insitem;

       scanf(" %d", &insitem);

       push( insitem, st );

       printf(" pushed %d. The current top item is %d\n", insitem,

           top_element(st) );

     } 

     if( nextop == 'd' )

     { int de_item;

       getchar();

       de_item = pop(st);

       printf("  popped item %d", de_item);

       if( stack_empty(st) )

         printf(" the stack is now empty\n");

       else

         printf(" the top element is now %d\n",  top_element(st) );


     }

     if( nextop == '?' )

     { getchar();

       if( stack_empty(st) )

         printf("the stack is empty\n");

       else

         printf("the top element is %d\n", top_element(st) );

     }

    

   }

   remove_stack(st);

   printf(" removed stack\n");

   return(0);

}

The author claims that his book is not a text on Object Oriented Programming, but instead a text on advanced data structures.  None of the examples in the URL referenced above employ C header files. I assume that was done to simplify publication, placing both the data structure, including its associated functions, and a main program to demonstrate the structure, in a single file.

Issues with ArrayStack.c

While the author could have used array indexing to access the values of the array at the heart of the stack_t struct, he chose to perform pointer arithmetic directly. From a style point of view it is often preferred to use array indexing to access the elements of an array.

The push function

Example 2 push function
int push( item_t x, stack_t *st)

{   if ( st->top < st->base + st->size )

    {   *(st->top) = x; st->top += 1;  return( 0 );

    }

    else

       return( -1 );

}

Note that this code only actually pushes a value onto the stack if the stack is not full. This is a correct behavior. When the array is full this function returns -1.
Example 3 Calling push
   while( (nextop = getchar())!= 'q' )

   { if( nextop == 'i' )

     { int insitem;

       scanf(" %d", &insitem);

       push( insitem, st );

       printf(" pushed %d. The current top item is %d\n", insitem,

           top_element(st) );

     } 


The example  of calling the push function ignores the return value. This means that the push operation fails silently. The user is given no indication that the push fails. In fact, the printf statement following the push function call tells the user that the push succeeds, whether it does nor not.

The pop function

Example 4 The pop function
item_t pop(stack_t *st)

{   st->top -= 1;

    return( *(st->top) );

}

Please note that the pop function does not check for an empty stack. Logically, it is erroneous to pop a value from an empty stack. This function simply decrements the pointer st->top and returns whatever value is at that memory location. Since no check is made for an empty stack, the result can be to return the value at some memory location before the first element of the dynamically allocated array pointed to by st->base. This is a form of buffer overflow.
The main function calls the pop function before checking if the stack is empty. Checking the state of the stack before calling pop would be the correct behavior. Nothing in the main function prevents pop from being called when the stack is empty.
     if( nextop == 'd' )

     { int de_item;

       getchar();

       de_item = pop(st);

       printf("  popped item %d", de_item);

       if( stack_empty(st) )

         printf(" the stack is now empty\n");

       else

         printf(" the top element is now %d\n",  top_element(st) );


     }

If the pop function is called several times when the stack is empty the st->top pointer will continue to be decremented. If, after that the push function is called, the pushed value will be assigned to a memory location before the first element of the array, corrupting memory outside of the array.

The top_element function

A somewhat milder version of the problem with the pop function is present in the top_element function. This function returns the value of in the address one less than the current st-top pointer.
Example 5 The top_element function
item_t top_element(stack_t *st)

{   return( *(st->top -1) );

}

As you can see, the top_element function does not check for an empty stack.

The stack_empty function

Example 6 The stack_empty function
int stack_empty(stack_t *st)

{   return( st->base == st->top );

}

Note that this function will only return True when st->top points to the first element of the allocated array. It will return False when st->top points to any memory location before the beginning of the array. Since the function pop can move the pointer st->top to memory locations before the beginning of the array this function is highly unreliable.

Conclusions concerning ArrayStack.c

While this code may convey the general approach to implementing an array-based stack data structure, the actual implementation is faulty in many ways. All of the faults are associated with the primitive implementation of arrays in the C language and what seems to be a cultural tendency for C programmers to avoid dealing with error conditions. C arrays are simply a block of memory accessed by pointers. C provides no array bounds checking. Failing to specify error conditions is inexcusable, as is failing to evaluate return values of functions that do check for error conditions.
The code for this implementation may execute very efficiently, but it does no good when the code is executing erroneously.

Bounded_Stack

The Ada code for a bounded stack type is shown below. The following example implements a generic stack using an array with the size of the array determined at compile time.
Ada requires a package to implement both a specification, which is analogous to a C header file, and a body, which contains the implementation of the procedures and functions declared in the specification.

The file bounded_stack.ads

The file bounded_stack.ads contains the interface specification for a generic stack based upon an array.
generic

   type Element_Type is private;

   Default_Value : Element_Type;

package Bounded_Stack is

   type Stack(Size : Positive) is tagged private;

   function Is_Empty(Item : Stack) return Boolean;

   function Is_Full(Item : Stack) return Boolean;

   procedure Push(Item : in out Stack; Value : in Element_Type) with

     Pre => not Is_Full(Item),

     Post => not Is_Empty(Item);

   procedure Pop(Item : in out Stack; Value : out Element_Type) with

     Pre => not Is_Empty(Item),

     Post => not Is_Full(Item);

   function Top(Item : in Stack) return Element_Type with

     Pre => not Is_Empty(Item);

   procedure Clear(Item : in out Stack) with

     Post => Is_Empty(Item);

private

   type Buffer is array(Positive range <>) of Element_Type;

   type Stack(Size : Positive) is tagged record

      Buf   : Buffer(1..Size) := (Others => Default_Value);

      Index : Positive := 1;

      Count : Natural  := 0;

   end record;

end Bounded_Stack;

This is a generic package, as indicated by the reserved word “generic” at the start of the file. This package has two generic parameters; Element_Type, which designates any non-limited type, and Default_Value, which is used to initialize every instance of the stack with a properly define default value.
Declaring the type Stack to be private means that the details of the type are hidden from any calling subprogram or task. The subprogram declarations following the type declaration, and preceding the reserved word “private” are the only means given to manipulate the stack.

The procedure Push

Example 7 The procedure Push
   procedure Push(Item : in out Stack; Value : in Element_Type) with

     Pre => not Is_Full(Item),

     Post => not Is_Empty(Item);

The declaration of the procedure Push contains a lot of information.
The parameter Item must be an instance of the type Stack. The procedure Item will be modified during the execution of the procedure Push. The parameter Value is an instance of Element_Type, and it will not be modified during execution of the procedure Push.
The procedure Push has a simple pre-condition, namely that the instance of Stack passed to this procedure cannot be full before calling Push.
The procedure Push has a simple post-condition, namely that the instance of Stack passed to the procedure Push will not be empty upon completion of the procedure Push.
The pre-condition and the post-condition are enforced by the compiler.

The procedure Pop

Example 8 The procedure Pop
   procedure Pop(Item : in out Stack; Value : out Element_Type) with

     Pre => not Is_Empty(Item),

     Post => not Is_Full(Item);

The procedure Pop also contains a lot of information.
The parameter Item is an instance of Stack which will be modified during the execution of the procedure Pop.
The parameter Value is an instance of Element_Type which will be set during the execution of the procedure Pop.
The pre-condition for procedure Pop is that the instance of Stack passed to the procedure cannot be empty.
The post-condition for procedure Pop is that the instance of Stack passed to the procedure will not be full upon completion of the procedure Pop.

The function Top

The function Top returns the value of the stack top stack element without changing the stack itself.
Example 9 The function Top
   function Top(Item : in Stack) return Element_Type with

     Pre => not Is_Empty(Item);

The parameter Item is an instance of the type Stack which is not modified during execution of the function Top.
The pre-condition for the function Top is that the instance of Stack passed to the function cannot be empty.

The procedure Clear

The procedure clear empties a stack.

   procedure Clear(Item : in out Stack) with

     Post => Is_Empty(Item);

Clear modifies the instance of Stack passed to it.
The post-condition specifies that the instance of Stack will be empty upon completion of the procedure Clear.

The file Bounded_Stack.adb

The file Bounded_Stack.adb contains the implementation of all the functions and procedures declared in the package specification.
All the code within the package body contained in the file Bounded_Stack.adb has visibility to all the code in the file Bounded_Stack.ads.
package body Bounded_Stack is


   --------------

   -- Is_Empty --

   --------------


   function Is_Empty (Item : Stack) return Boolean is

   begin

      return Item.Count = 0;

   end Is_Empty;


   -------------

   -- Is_Full --

   -------------


   function Is_Full (Item : Stack) return Boolean is

   begin

      return Item.Count = Item.Size;

   end Is_Full;


   ----------

   -- Push --

   ----------


   procedure Push(Item : in out Stack; Value : in Element_Type) is

   begin

      Item.Buf(Item.Index) := Value;

      Item.Index := Item.Index + 1;

      Item.Count := Item.Count + 1;

   end Push;


   ---------

   -- Pop --

   ---------


   procedure Pop (Item : in out Stack; Value : out Element_Type) is

   begin

      Value := Item.Top;

      Item.Index := Item.Index - 1;

      Item.Count := Item.Count - 1;

   end Pop;


   ---------

   -- Top --

   ---------


   function Top (Item : in Stack) return Element_Type is

   begin

      return Item.Buf(Item.Index - 1);

   end Top;


   -----------

   -- Clear --

   -----------


   procedure Clear(Item : in out Stack) is

   begin

      Item.Count := 0;

      Item.Index := 1;

   end Clear;


end Bounded_Stack;

You may notice that the implementations of the functions and procedures in Bounded_Stack.adb are very simple. There is no explicit check for empty or full stack conditions. The pre-conditions specified in each procedure or function specification are implicitly checked by code generated by the compiler. The post-conditions specified for each procedure or function are implicitly checked by code generated by the compiler. Failure of any specified pre-condition or post-condition results in an exception which.if unhandled,  terminates the program.

The file main.adb

with Bounded_Stack;

with Ada.Text_IO; use Ada.Text_IO;

with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;


procedure Main is

   package Int_Stack is new Bounded_Stack(Element_Type  => Integer,

                                          Default_Value => 0);

   use Int_Stack;

   My_Stack : Stack(Size => 10);

   Value : Integer;

begin

   while not My_Stack.Is_Full loop

      Put("Enter an integer: ");

      Get(Value);

      My_Stack.Push(Value);

   end loop;

   Put_Line("Printing and popping the stack:");

   while not My_Stack.Is_Empty loop

      My_Stack.Pop(Value);

      Put_Line(Integer'Image(Value));

   end loop;

   My_Stack.Pop(Value);

   Put_Line(Integer'Image(Value));

end Main;

The output of this program is:

Enter an integer: 1
Enter an integer: 2
Enter an integer: 3
Enter an integer: 4
Enter an integer: 5
Enter an integer: 6
Enter an integer: 7
Enter an integer: 8
Enter an integer: 9
Enter an integer: 0
Printing and popping the stack:
 0
 9
 8
 7
 6
 5
 4
 3
 2
 1

raised SYSTEM.ASSERTIONS.ASSERT_FAILURE : failed precondition from bounded_stack.ads:12 instantiated at main.adb:6
The main procedure pops all the values off the stack then attempts to pop one more value off the stack. Rather than corrupting the stack pointer the program raises the exception SYSTEM.ASSERTIONS.ASSERT_FAILURE. The exception message points to the pre-condition for the pop procedure requiring the stack to be not empty.

  • 11 June 2018 at 23:03

Comparison of Array Based Stacks in C and Ada


Comparison of Array Based Stacks in C and Ada

The stack is one of the simplest data structures. Array-based stacks can be implemented in all languages supporting arrays, even including early versions of Fortran which did not support pointers.

This comparison is based upon C code published at http://www-cs.ccny.cuny.edu/~peter/dstest.html
The C code published at http://www-cs.ccny.cuny.edu/~peter/dstest.html supports the book Advanced Data Structures authored by Peter Braß and published by Cambridge University Press.


The Ada code uses Ada2012, which added aspect specifications, pre and post conditions, type invariants and subtype predicates to the Ada language. Descriptions of these and other features added in the Ada 2012 version are available at The Ada 2012 Rationale

Array Stack

The C code for Array Stack is shown below.

Example 1 ArrayStack.c
#include <stdio.h>

#include <stdlib.h>


typedef int item_t;


typedef struct {item_t *base; item_t *top; int size;} stack_t;


stack_t *create_stack(int size)

{   stack_t *st;

    st = (stack_t *) malloc( sizeof(stack_t) );

    st->base = (item_t *) malloc( size * sizeof(item_t) );

    st->size = size;

    st->top = st->base;

    return( st );

}


int stack_empty(stack_t *st)

{   return( st->base == st->top );

}


int push( item_t x, stack_t *st)

{   if ( st->top < st->base + st->size )

    {   *(st->top) = x; st->top += 1;  return( 0 );

    }

    else

       return( -1 );

}


item_t pop(stack_t *st)

{   st->top -= 1;

    return( *(st->top) );

}


item_t top_element(stack_t *st)

{   return( *(st->top -1) );

}


void remove_stack(stack_t *st)

{   free( st->base );

    free( st );

}




int main()

{  stack_t *st;

   char nextop;

   st = create_stack(50);

   printf("Made Array-Based Stack of size 50\n");

   while( (nextop = getchar())!= 'q' )

   { if( nextop == 'i' )

     { int insitem;

       scanf(" %d", &insitem);

       push( insitem, st );

       printf(" pushed %d. The current top item is %d\n", insitem,

           top_element(st) );

     } 

     if( nextop == 'd' )

     { int de_item;

       getchar();

       de_item = pop(st);

       printf("  popped item %d", de_item);

       if( stack_empty(st) )

         printf(" the stack is now empty\n");

       else

         printf(" the top element is now %d\n",  top_element(st) );


     }

     if( nextop == '?' )

     { getchar();

       if( stack_empty(st) )

         printf("the stack is empty\n");

       else

         printf("the top element is %d\n", top_element(st) );

     }

    

   }

   remove_stack(st);

   printf(" removed stack\n");

   return(0);

}

The author claims that his book is not a text on Object Oriented Programming, but instead a text on advanced data structures.  None of the examples in the URL referenced above employ C header files. I assume that was done to simplify publication, placing both the data structure, including its associated functions, and a main program to demonstrate the structure, in a single file.

Issues with ArrayStack.c

While the author could have used array indexing to access the values of the array at the heart of the stack_t struct, he chose to perform pointer arithmetic directly. From a style point of view it is often preferred to use array indexing to access the elements of an array.

The push function

Example 2 push function
int push( item_t x, stack_t *st)

{   if ( st->top < st->base + st->size )

    {   *(st->top) = x; st->top += 1;  return( 0 );

    }

    else

       return( -1 );

}

Note that this code only actually pushes a value onto the stack if the stack is not full. This is a correct behavior. When the array is full this function returns -1.
Example 3 Calling push
   while( (nextop = getchar())!= 'q' )

   { if( nextop == 'i' )

     { int insitem;

       scanf(" %d", &insitem);

       push( insitem, st );

       printf(" pushed %d. The current top item is %d\n", insitem,

           top_element(st) );

     } 


The example  of calling the push function ignores the return value. This means that the push operation fails silently. The user is given no indication that the push fails. In fact, the printf statement following the push function call tells the user that the push succeeds, whether it does nor not.

The pop function

Example 4 The pop function
item_t pop(stack_t *st)

{   st->top -= 1;

    return( *(st->top) );

}

Please note that the pop function does not check for an empty stack. Logically, it is erroneous to pop a value from an empty stack. This function simply decrements the pointer st->top and returns whatever value is at that memory location. Since no check is made for an empty stack, the result can be to return the value at some memory location before the first element of the dynamically allocated array pointed to by st->base. This is a form of buffer overflow.
The main function calls the pop function before checking if the stack is empty. Checking the state of the stack before calling pop would be the correct behavior. Nothing in the main function prevents pop from being called when the stack is empty.
     if( nextop == 'd' )

     { int de_item;

       getchar();

       de_item = pop(st);

       printf("  popped item %d", de_item);

       if( stack_empty(st) )

         printf(" the stack is now empty\n");

       else

         printf(" the top element is now %d\n",  top_element(st) );


     }

If the pop function is called several times when the stack is empty the st->top pointer will continue to be decremented. If, after that the push function is called, the pushed value will be assigned to a memory location before the first element of the array, corrupting memory outside of the array.

The top_element function

A somewhat milder version of the problem with the pop function is present in the top_element function. This function returns the value of in the address one less than the current st-top pointer.
Example 5 The top_element function
item_t top_element(stack_t *st)

{   return( *(st->top -1) );

}

As you can see, the top_element function does not check for an empty stack.

The stack_empty function

Example 6 The stack_empty function
int stack_empty(stack_t *st)

{   return( st->base == st->top );

}

Note that this function will only return True when st->top points to the first element of the allocated array. It will return False when st->top points to any memory location before the beginning of the array. Since the function pop can move the pointer st->top to memory locations before the beginning of the array this function is highly unreliable.

Conclusions concerning ArrayStack.c

While this code may convey the general approach to implementing an array-based stack data structure, the actual implementation is faulty in many ways. All of the faults are associated with the primitive implementation of arrays in the C language. C arrays are simply a block of memory accessed by pointers. C provides no array bounds checking.
The code for this implementation may execute very efficiently, but it does no good when the code is executing erroneously.

Bounded_Stack

The Ada code for an unbounded stack type is shown below. The following example implements a generic stack using an array with the size of the array determined at compile time.
Ada requires a package to implement both a specification, which is analogous to a C header file, and a body, which contains the implementation of the procedures and functions declared in the specification.

The file bounded_stack.ads

The file bounded_stack.ads contains the interface specification for a generic stack based upon an array.
generic

   type Element_Type is private;

   Default_Value : Element_Type;

package Bounded_Stack is

   type Stack(Size : Positive) is tagged private;

   function Is_Empty(Item : Stack) return Boolean;

   function Is_Full(Item : Stack) return Boolean;

   procedure Push(Item : in out Stack; Value : in Element_Type) with

     Pre => not Is_Full(Item),

     Post => not Is_Empty(Item);

   procedure Pop(Item : in out Stack; Value : out Element_Type) with

     Pre => not Is_Empty(Item),

     Post => not Is_Full(Item);

   function Top(Item : in Stack) return Element_Type with

     Pre => not Is_Empty(Item);

   procedure Clear(Item : in out Stack) with

     Post => Is_Empty(Item);

private

   type Buffer is array(Positive range <>) of Element_Type;

   type Stack(Size : Positive) is tagged record

      Buf   : Buffer(1..Size) := (Others => Default_Value);

      Index : Positive := 1;

      Count : Natural  := 0;

   end record;

end Bounded_Stack;

This is a generic package, as indicated by the reserved word “generic” at the start of the file. This package has two generic parameters; Element_Type, which designates any non-limited type, and Default_Value, which is used to initialize every instance of the stack with a properly define default value.
Declaring the type Stack to be private means that the details of the type are hidden from any calling subprogram or task. The subprogram declarations following the type declaration, and preceding the reserved word “private” are the only means given to manipulate the stack.

The procedure Push

Example 7 The procedure Push
   procedure Push(Item : in out Stack; Value : in Element_Type) with

     Pre => not Is_Full(Item),

     Post => not Is_Empty(Item);

The declaration of the procedure Push contains a lot of information.
The parameter Item must be an instance of the type Stack. The parameter Item will be modified during the execution of the procedure Push. The parameter Value is an instance of Element_Type, and it will not be modified during execution of the procedure Push.
The procedure Push has a simple pre-condition, namely that the instance of Stack passed to this procedure cannot be full before calling Push.
The procedure Push has a simple post-condition, namely that the instance of Stack passed to the procedure Push will not be empty upon completion of the procedure Push.
The pre-condition and the post-condition are enforced by the compiler.

The procedure Pop

Example 8 The procedure Pop
   procedure Pop(Item : in out Stack; Value : out Element_Type) with

     Pre => not Is_Empty(Item),

     Post => not Is_Full(Item);

The procedure Pop also contains a lot of information.
The parameter Item is an instance of Stack which will be modified during the execution of the procedure Pop.
The parameter Value is an instance of Element_Type which will be set during the execution of the procedure Pop.
The pre-condition for procedure Pop is that the instance of Stack passed to the procedure cannot be empty.
The post-condition for procedure Pop is that the instance of Stack passed to the procedure will not be full upon completion of the procedure Pop.

The function Top

The function Top returns the value of the stack top stack element without changing the stack itself.
Example 9 The function Top
   function Top(Item : in Stack) return Element_Type with

     Pre => not Is_Empty(Item);

The parameter Item is an instance of the type Stack which is not modified during execution of the function Top.
The pre-condition for the function Top is that the instance of Stack passed to the function cannot be empty.

The procedure Clear

The procedure clear empties a stack.

   procedure Clear(Item : in out Stack) with

     Post => Is_Empty(Item);

Clear modifies the instance of Stack passed to it.
The post-condition specifies that the instance of Stack will be empty upon completion of the procedure Clear.

The file Bounded_Stack.adb

The file Bounded_Stack.adb contains the implementation of all the functions and procedures declared in the package specification.
All the code within the package body contained in the file Bounded_Stack.adb has visibility to all the code in the file Bounded_Stack.ads.
package body Bounded_Stack is


   --------------

   -- Is_Empty --

   --------------


   function Is_Empty (Item : Stack) return Boolean is

   begin

      return Item.Count = 0;

   end Is_Empty;


   -------------

   -- Is_Full --

   -------------


   function Is_Full (Item : Stack) return Boolean is

   begin

      return Item.Count = Item.Size;

   end Is_Full;


   ----------

   -- Push --

   ----------


   procedure Push(Item : in out Stack; Value : in Element_Type) is

   begin

      Item.Buf(Item.Index) := Value;

      Item.Index := Item.Index + 1;

      Item.Count := Item.Count + 1;

   end Push;


   ---------

   -- Pop --

   ---------


   procedure Pop (Item : in out Stack; Value : out Element_Type) is

   begin

      Value := Item.Top;

      Item.Index := Item.Index - 1;

      Item.Count := Item.Count - 1;

   end Pop;


   ---------

   -- Top --

   ---------


   function Top (Item : in Stack) return Element_Type is

   begin

      return Item.Buf(Item.Index - 1);

   end Top;


   -----------

   -- Clear --

   -----------


   procedure Clear(Item : in out Stack) is

   begin

      Item.Count := 0;

      Item.Index := 1;

   end Clear;


end Bounded_Stack;

You may notice that the implementations of the functions and procedures in Bounded_Stack.adb are very simple. There is no explicit check for empty or full stack conditions. The pre-conditions specified in each procedure or function specification are implicitly checked by code generated by the compiler. The post-conditions specified for each procedure or function are implicitly checked by code generated by the compiler. Failure of any specified pre-condition or post-condition results in an exception which, if unhandled,  terminates the program.

The file main.adb

with Bounded_Stack;

with Ada.Text_IO; use Ada.Text_IO;

with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;


procedure Main is

   package Int_Stack is new Bounded_Stack(Element_Type  => Integer,

                                          Default_Value => 0);

   use Int_Stack;

   My_Stack : Stack(Size => 10);

   Value : Integer;

begin

   while not My_Stack.Is_Full loop

      Put("Enter an integer: ");

      Get(Value);

      My_Stack.Push(Value);

   end loop;

   Put_Line("Printing and popping the stack:");

   while not My_Stack.Is_Empty loop

      My_Stack.Pop(Value);

      Put_Line(Integer'Image(Value));

   end loop;

   My_Stack.Pop(Value);

   Put_Line(Integer'Image(Value));

end Main;

The output of this program is:

Enter an integer: 1
Enter an integer: 2
Enter an integer: 3
Enter an integer: 4
Enter an integer: 5
Enter an integer: 6
Enter an integer: 7
Enter an integer: 8
Enter an integer: 9
Enter an integer: 0
Printing and popping the stack:
 0
 9
 8
 7
 6
 5
 4
 3
 2
 1

raised SYSTEM.ASSERTIONS.ASSERT_FAILURE : failed precondition from bounded_stack.ads:12 instantiated at main.adb:6
The main procedure pops all the values off the stack then attempts to pop one more value off the stack. Rather than corrupting the stack pointer the program raises the exception SYSTEM.ASSERTIONS.ASSERT_FAILURE. The exception message points to the pre-condition for the pop procedure requiring the stack to be not empty.

  • 29 August 2018 at 14:16

Ada 2012 Static Predicates


1.     Subtypes

Every Ada type is associated with a subtype. A subtype is a subset of the base type, commonly with some restriction in the set of values valid for the subtype. For instance, Ada provides a pre-defined type named Integer. Integer is a signed type representing all the values in the range of -2**31..2**31 – 1. Ada also provides two pre-defined subtypes of Integer: Natural, which is an integer with a minimum value of 0, and Positive which is an integer with a minimum value of 1. Every instance of Natural or Positive is also an instance of Integer. This capability to define subtypes has been a part of Ada since the first Ada language standard in 1983.
While this subtype capability has been very useful, it had some restrictions. Subtype ranges were always restricted to contiguous ranges. For instance, the syntax for defining the Natural subtype mentioned above is:
subtype Natural is Integer range 0..Integer’Last;

 Integer’Last evaluates to the highest valid value defined for the type Integer.

2.     Static Predicates

Ada 2012 added new capabilities to the definition of subtypes. One of those capabilities is the definition of static predicates. Static predicates allow the definition of statically defined non-contiguous sets of values.
For instance, a subtype of the pre-defined type Character can be defined to contain only the letters in the ASCII subset of Character:
subtype Letters is Character with Static_Predicate => ‘A’..’Z’ | ‘a’..’z’;

Notice that the subtype Letters contains two contiguous ranges. One range includes the upper case letters and the other contains the lower case letters. Similarly one can define a subtype with no contiguous ranges of values such as:
subtype Vowels is Character with Static_Predicate => ‘A’ | ‘E’ | ‘I’ | ‘O’ | ‘U’ | ‘a’ | ‘e’ | ‘i’ | ‘o’ | ’u’;

2.1. Example

Following are two programs used to calculate the number of vowels and consonants in a string read from standard input. The C code is taken from https://www.sanfoundry.com/c-program-count-number-vowels-consonants-sentence/
Both programs achieve the same goal with some problems in the C code dealing with punctuation and tab characters.

2.1.1.     C Code

/*

 * C program to read a sentence and count the total number of vowels

 * and consonants in the sentence.

 */

#include <stdio.h>


void main()

{

    char sentence[80];

    int i, vowels = 0, consonants = 0, special = 0;


    printf("Enter a sentence \n");

    gets(sentence);

    for (i = 0; sentence[i] != '\0'; i++)

    {

        if ((sentence[i] == 'a' || sentence[i] == 'e' || sentence[i] ==

        'i' || sentence[i] == 'o' || sentence[i] == 'u') ||

        (sentence[i] == 'A' || sentence[i] == 'E' || sentence[i] ==

        'I' || sentence[i] == 'O' || sentence[i] == 'U'))

        {

            vowels = vowels + 1;

        }

        else

        {

            consonants = consonants + 1;

        }

        if (sentence[i] =='t' ||sentence[i] =='\0' || sentence[i] ==' ')

        {

            special = special + 1;

        }

    }

    consonants = consonants - special;

    printf("No. of vowels in %s = %d\n", sentence, vowels);

    printf("No. of consonants in %s = %d\n", sentence, consonants);

}

2.1.2.     Ada 2012 Code

-----------------------------------------------------------------------

-- Count the number of vowels and consonants in an input string

-----------------------------------------------------------------------

with Ada.Text_IO; use Ada.Text_Io;


procedure Count_Letters is

   subtype Vowels is Character with

     Static_Predicate => Vowels in

       'A' | 'E' | 'I' | 'O' | 'U' | 'a' | 'e' | 'i' | 'o' | 'u';

   subtype Letters is Character with

     Static_Predicate => Letters in 'A'..'Z' | 'a'..'z';

   Msg         : String(1..1024);

   Length      : Natural;

   Cons_Count  : Natural := 0;

   Vowel_Count : Natural := 0;

begin

   Put("Enter phrase: ");

   Get_Line(Msg, Length);

   for Char of Msg(1..Length) loop

      if Char in Vowels then

         Vowel_Count := Vowel_Count + 1;

      elsif Char in Letters then

         Cons_Count := Cons_Count + 1;

      end if;

   end loop;

   Put_Line('"' & Msg(1..Length) & '"' & " contains" & Vowel_Count'Image &

              " vowels and" & Cons_Count'Image & " consonants.");

end Count_Letters;


The subtypes Vowels and Letters are used to define sets of values. The “if” statement tests each character of the input string for membership in one set or the other. All characters not in either set, such as spaces or punctuation, are not counted. Note that the C version of the program counts all characters which are not vowels then subtracts specified non-letter characters. The C code will actually count punctuation as consonants because it does not include those characters in its list of special characters. On the other hand the Ada code contains positive definitions of the characters to be counted and only counts the specified characters. This ability to unambiguously define the set of characters to count eliminates a set of errors hidden in the C code.

  • 5 October 2018 at 16:06

Ada Concurrency



Sequential Programming

Traditional programs are sequential, performing actions in a specified sequence. The classical pattern for sequential program execution is described as Input, Process, Output. This pattern may be performed once or it may be performed many times using either iteration or recursion. This pattern is very effective at doing one thing at a time for one user. The pattern exhibits serious limitations when dealing with multiple events overlapping in time, or with multiple users.
For instance, the following sequential program calculates the sum of the digits in an integer. The program supports one user who enters a single number.
1  Simple Sequential Program
-----------------------------------------------------------------------

-- calculate the sum of the digits of a positive integer

-----------------------------------------------------------------------

with Ada.Text_IO; use Ada.Text_Io;

with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;


procedure Sum_Digits is

   Num : Natural;

   Sum : Integer := 0;

begin


   Put("Enter a positive integer: ");

   Get(Num);


   Put_Line("Given number ="& Num'Image);


   while Num > 0 loop

      Sum := Sum + (Num mod 10);

      Num := Num / 10;

   end loop;


   Put_Line("Sum of the digits =" & Sum'Image);


end Sum_Digits;

Concurrent Programming

Computer designers soon realized that some problems required more than one program to run at a time and some business needs required more than one person at a time to use computing resources. The first solution for this problem was called time sharing. Operating systems were developed that allowed several programs to take turns interleaving their sequential processing so that each program could make some progress in its sequential actions during a given time period. This provided the illusion of concurrent behavior and also more efficiently used computer resources. Using the Input, Process, Output model each program would have periods of low activity while waiting for input or output data or resources. Other programs could be allowed to execute during these “waiting” periods.
These early solutions still only used a single central processor since computers of the time only had a single central processor. The time sharing approach allowed multiple users to run many different programs or many instances of the same program simultaneously, but performance was noticeably impacted as the number of simultaneous users increased. Some systems could support a dozen users while others could support 50 to 100 users before performance became bothersome.
Operating systems such as Unix implemented concepts of pipes and filters allowing many programs to chain input and output to perform complex processing. Unix pipes were I/O channels within the computer providing guaranteed delivery of data from one program to another.
For example, if a user wanted to count the number of files in a directory the user could combine two Unix filters or programs. The “ls” program lists the files in a directory. The “wc” program counts words or lines read from its input and outputs the count. The user would simply write
ls | wc –l

The “|” symbol was used to implement a pipe between ls and wc. The output of the ls command is written to the pipe and that output is sent to the input of the wc command. The concept was thought of as plumbing data from one program to another through a “pipe”. The command wc –l caused the wc program to count only lines, not words or characters. The ls command output the list of files in the directory with one file name per line of output. The pipe concept was very sophisticated for its time. The pipe actually caused the two programs to synchronize their processing. If the pipe was empty the reading program would be suspended until data arrived. If the pipe was full the writing program would be suspended until data was consumed by the reading program.
The first attempts to increase performance through hardware involved locating two or more computer motherboards in the same computer chassis. Sequential programs could then be distributed across the two or more central processors to provide increased processing capability to programs and users. One of the problems of this early approach is that pipes did not initially work across computer boundaries. A program executing on one motherboard could not pipe data to a program executing on the other motherboard. Networking was needed to communicate data between motherboards.
Eventually multi-core processors were developed along with multi-tasking cores, allowing parallel tasks to be executed on the same central processor. Those tasks can communicate directly or through shared memory without the complexities and delays of networking.

Tasks

The commonly used term for separately executing sub-processes is threads, while the academic term for has long been tasks. The Ada language has supported the creation of tasks since its first language standard in 1983, long before the term “threads” became popular for sub-processes.
Every Ada program executes at least one task, which is commonly called the main task. Additional tasks can be created. The example below shows a main task which creates a child task. Both the child task and the main task run at the same time.
2 Simple Task
with Ada.Text_IO; use Ada.Text_IO;


procedure Simple_Task_Main is

   task Hello_Task;

  

   task body Hello_Task is

   begin


      for I in 1..10 loop

         Put_Line("=== Hello from the child task.===");

      end loop;


   end Hello_Task;


begin


   for I in 1..11 loop

      Put_Line("Hello from the main task.");

   end loop;


end Simple_Task_Main;


The interleaving of the outputs from the two tasks will vary from one execution to another. Following is an output of this program.
3 Simple Task Output
=== Hello from the child task.===

Hello from the main task.

=== Hello from the child task.===

Hello from the main task.

=== Hello from the child task.===

Hello from the main task.

=== Hello from the child task.===

Hello from the main task.

=== Hello from the child task.===

Hello from the main task.

=== Hello from the child task.===

Hello from the main task.

=== Hello from the child task.===

Hello from the main task.

=== Hello from the child task.===

Hello from the main task.

=== Hello from the child task.===

Hello from the main task.

=== Hello from the child task.===

Hello from the main task.

Hello from the main task.


The child task automatically starts when execution of the main task reaches the “begin” statement in the main task.

Task Interactions

The two tasks shown above do not share any data. Most useful concurrent programs need to share data among the tasks. Ada provides two approaches for sharing data. The Ada Rendezvous mechanism allows two tasks to pass data from one to another in a synchronous manner. Ada Protected Objects allow data to be shared asynchronously through a shared buffer.

Rendezvous

A task may provide entries. Entries may be called by other tasks to pass data between the called task and the calling task. The two tasks synchronize at the calling/called points. If the caller gets to the entry interface first the caller waits for the called task. If the called task gets to the entry interface point first the called task waits for the calling task.
The following example demonstrates the use of tasks to perform parallel addition of the elements of an array. The calling task passes the first half of an array to one task and the second half of the same array to another task. Each task totals the elements in its portion of the array and passes back the sum. The calling task then retrieves the two sums, and calculates the final sum of array elements. This example also calculates the time required to perform the parallel sum and displays that timing as well as the result.
The parallel sum routine is provided in a separate Ada package. The package specification identifies the data types and function interface required to call the parallel addition routine. The package body defines the executable code for the parallel addition routine. Finally, a main procedure is defined to test the parallel addition and record execution times.
4 Parallel Addition Specification
package Parallel_Addition is

   type Data_Array is array(Integer range <>) of Integer;

   type Data_Access is access all Data_Array;


   function Sum(Item : in not null Data_Access) return Integer;


end Parallel_Addition;


The package specification defines two data types. Data_Array is an unconstrained array of Integer. Data_Access is an access type referencing Data_Array. Access types are roughly equivalent to references in C++.
The function Sum takes a non-null instance of Data_Access as an input parameter and returns an Integer; Use of an access type as the parameter allows the program to handle very large arrays of integer very efficiently.
5 Parallel Addition Body
package body Parallel_Addition is


   ---------

   -- Sum --

   ---------


   function Sum (Item : in not null Data_Access) return Integer is

      task type Adder is

         entry Set (Min : Integer; Max : Integer);

         entry Report (Value : out Integer);

      end Adder;


      task body Adder is

         Total : Integer := 0;

         First : Integer;

         Last  : Integer;

      begin


         accept Set (Min : Integer; Max : Integer) do

            First := Min;

            Last  := Max;

         end Set;


         for I in First .. Last loop

            Total := Total + Item (I);

         end loop;


         accept Report (Value : out Integer) do

            Value := Total;

         end Report;


      end Adder;


      A1  : Adder;

      A2  : Adder;

      R1  : Integer;

      R2  : Integer;

      Mid : constant Integer := (Item'Length / 2) + Item'First;


   begin


      A1.Set (Min => Item'First, Max => Mid);

      A2.Set (Min => Mid + 1, Max => Item'Last);

      A1.Report (R1);

      A2.Report (R2);

      return R1 + R2;


   end Sum;


end Parallel_Addition;


The package body for Parallel_Addition contains the implementation of the function Sum. Inside the function Sum a task type named Adder is defined. First the interface for Adder declares that it has two entries. The entry named Set reads in two parameters Min and Max, which are the array indices an instance of Adder will use to access elements of the array pointed to by the parameter Item passed into function Sum. The entry named Report passes a single integer out to the calling task of the Adder instance.
The task body of the Adder task type accepts the Set entry, reading the Min and Max values and assigning them to the local variables First and Last. The task then sums all the values from index First to index Last. Finally, the Adder task accepts the Report entry and passes its total to the calling task.
The calling task in this case is the task that calls the Sum function.
Two instances of the Adder task, named A1 and A2, are created. Both instances start executing when the Sum function reaches its “begin” statement. Both tasks suspend at the accept statement for the Set entry, waiting for some task to call them. The executable part of the Sum function then calls the Set entry for each task instance, passing in the appropriate Min and Max values. The Sum function then calls the Report entries of each task. The Sum function waits for the tasks to execute the Accept call for the Report entry, then adds the two reported values and returns that final total.
The main procedure for this example is:
6 Parallel Addition Test Main Procedure
with Parallel_Addition; use Parallel_Addition;

with Ada.Text_IO;       use Ada.Text_IO;

with Ada.Calendar;      use Ada.Calendar;


procedure Parallel_Addition_Test is

   The_Data : Data_Access := new Data_Array (1 .. Integer'Last / 5);

   Start    : Time;

   Stop     : Time;

   The_Sum  : Integer;


begin

   The_Data.all := (others => 1);


   Start        := Clock;

   The_Sum      := Sum (The_Data);

   Stop         := Clock;


   Put_Line ("The sum is: " & Integer'Image (The_Sum));

   Put_Line

     ("Addition elapsed time is " &

      Duration'Image (Stop - Start) &

        " seconds.");

   Put_Line

     ("Time per addition operation is " &

        Float'Image(Float(Stop - Start) / Float(The_Data'Length)) &

        " seconds.");

end Parallel_Addition_Test;


The test procedure dynamically allocates an instance of Data_Array containing 429496729 integers and assigns the value 1 to each element. A start time of the Sum function is assigned to the variable Start, the Sum function is called, and a stop time of the Sum function is assigned to the variable Stop. Finally the sum, the total execution time of the function, and the average time per addition are displayed.
7 Parallel Addition Test Output
The sum is:  429496729

Addition elapsed time is  1.147519700 seconds.

Time per addition operation is  2.67178E-09 seconds.


A common programming pattern for concurrency is the Producer-Consumer pattern. This is the pattern used for Unix pipes and filters, and variations on this pattern continue to be used in many programs. The simplest producer-consumer pattern employs a single producing task and a single consuming task. This simple producer-consumer is easily implemented using the Ada Rendezvous mechanism.
8 Producer Consumer Rendezvous
-----------------------------------------------------------------------

-- Producer Consumer implemented using the Ada Rendezvous

-----------------------------------------------------------------------

with Ada.Text_IO; use Ada.Text_Io;


procedure rendezvous_pc is

   task producer;


   task consumer is

      entry Put(Item : in Integer);

   end consumer;


   task body producer is

   begin

      for I in 0..15 loop

         Consumer.Put(I);

      end loop;

   end Producer;


   task body consumer is

      Num : Integer;

   begin

      loop

         select

            accept Put(Item : in Integer) do

               Num := Item;

            end Put;

            Put_Line("Consumed" & Num'Image);

         or

            terminate;

         end select;

      end loop;

   end consumer;


begin

   null;

end rendezvous_pc;


Program 9 Producer Consumer Rendezvous Output
Consumed 0

Consumed 1

Consumed 2

Consumed 3

Consumed 4

Consumed 5

Consumed 6

Consumed 7

Consumed 8

Consumed 9

Consumed 10

Consumed 11

Consumed 12

Consumed 13

Consumed 14

Consumed 15


In this example the main task only starts up the producer and consumer and then waits for them to complete. The producer generates 16 integer values from 0 through 15 then stops. The consumer reads each value produced by the producer and quits after the producer quits.

Protected Objects

The Rendezvous can be very useful in synchronous communication between tasks, but is very clumsy when developing asynchronous task communication solutions. Ada’s solution is the Protected Object.
Ada protected objects are shared data buffers protected from race conditions between tasks. Protected objects have three kinds of interfaces to tasks.
Protected Operation
Description
Function
Protected functions provide shared read-only access to the protected object. Multiple tasks can simultaneously call protected functions of a protected object. Functions implement a read lock on the protected object preventing any task from changing the object while it is being read.
Procedure
Protected procedures provide unconditional read/write access to a protected object. Protected procedures employ an exclusive read/write lock ensuring only one task at a time has access to the object during execution of the procedure.
Entries
Protected entries provide conditional read/write access to a protected object. Protected entries employ an exclusive read/write lock ensuring only one task at a time has access to the object during execution of the entry. Tasks calling a protected entry while the controlling condition is false are automatically suspended in an entry queue. Tasks in an entry queue are activated and released from the queue when the controlling condition evaluates to TRUE. The order in which tasks are released from the entry queue depends upon the chosen queuing policy. The default queuing policy is First In First Out.

Following is a simple producer-consumer example using a protected object.
10 Generic Protected Object Specification
generic

   type Element_Type is private;

package Protected_Buffer is


   Capacity : constant := 10;

   type Index_T is mod Capacity;

   type Internal_Buffer is array(Index_T) of Element_Type;


   protected type Buffer_T is

      entry Add(Item : in Element_Type);

      entry Get(Item : out Element_Type);

   private

      Buf : Internal_Buffer;

      Add_Idx : Index_T := Index_T'First;

      Get_Idx : Index_T := Index_T'First;

      Count   : Natural := 0;

   end Buffer_T;


end Protected_Buffer;


This protected object provides two operations. The Add entry and the Get entry allow a task to write to the protected object and to read from the protected object.
The Internal_Buffer type is an array indexed by a modular type. The valid index values for this array are 0 through 9. Modular types provide modular arithmetic. In this case 9 + 1 results in 0. Modular types are very useful for implementing circular buffers.
The implementation of the protected type is:
11 Protected Object Implementation
package body Protected_Buffer is


   --------------

   -- Buffer_T --

   --------------


   protected body Buffer_T is


      ---------

      -- Add --

      ---------


      entry Add (Item : in Element_Type) when Count < Capacity is

      begin

         Buf(Add_Idx) := Item;

         Add_Idx := Add_Idx + 1;

         Count := Count + 1;

      end Add;


      ---------

      -- Get --

      ---------


      entry Get (Item : out Element_Type) when Count > 0 is

      begin

         Item := Buf(Get_Idx);

         Get_Idx := Get_Idx + 1;

         Count := Count - 1;

      end Get;


   end Buffer_T;


end Protected_Buffer;


The protected body reveals the conditions associated with each protected entry. The Add entry is only open to execution when Count is less than Capacity. The Get entry is only open to execution when Count is greater than 0.
This implementation of a protected object is designed to ensure that every value written to the protected object can be read from the protected object. As we will see later, other behaviors are possible.
12 Protected Producer Consumer Main
with Ada.Text_IO; use Ada.Text_IO;

with Protected_Buffer;


procedure Main is

   package Int_Buf is new Protected_Buffer(Integer);

   use Int_Buf;

   Shared_Buf : Buffer_T;

   task producer;


   task body producer is

   begin

      for I in 0..15 loop

         Shared_Buf.Add(I);

      end loop;

   end producer;


   task consumer;

   task body consumer is

      Num : Integer;

   begin

      loop

         select

            Shared_Buf.Get(Num);

            Put_Line("Consumed" & Num'Image);

         or

            delay 0.001;

            exit;

         end select;

      end loop;

   end consumer;


begin

   null;

end Main;


13 Protected Main Output
Consumed 0

Consumed 1

Consumed 2

Consumed 3

Consumed 4

Consumed 5

Consumed 6

Consumed 7

Consumed 8

Consumed 9

Consumed 10

Consumed 11

Consumed 12

Consumed 13

Consumed 14

Consumed 15


In this example the producer and consumer tasks never directly communicate with each other. Instead, the two tasks only communicate with the protected object. The producer task adds values to the protected object and the consumer task gets values from the protected object.
Notice that all the lock manipulation is handled automatically.
There are many variations on the producer-consumer model. For instance, it is possible to have a producer that produces data faster than the consumer can consume data. If one uses the protected object implementation shown above the producer will be slowed down to the rate of the consumer because the buffer will eventually fill. While this behavior may be desirable under some circumstances, it may be unacceptable under other circumstances.
Some problem domains may require that the producer work at full speed, completely uninhibited by the consumer. The result is that the consumer must under-sample the data produced by the producer.
14 Undersampling Example
with Ada.Text_IO; use Ada.Text_IO;


procedure Undersampling is

   protected Buffer is

      procedure Add(Item : Integer);

      entry Get(Item : out Integer);

   private

      Value : Integer;

      Is_New : Boolean := False;

   end Buffer;

  

   protected body Buffer is

     

      procedure Add(Item : Integer) is

      begin

         Value := Item;

         Is_New := True;

      end Add;

     

      entry Get(Item : out Integer) when Is_New is

      begin

         Item := Value;

         Is_New := False;

      end Get;

   end Buffer;

  

   task Producer is

      Entry Stop;

   end Producer;

  

   task Consumer is

      Entry Stop;

   end Consumer;

  

   task body Producer is

      Num : Natural := 0;

   begin

      loop

         select

            Accept Stop;

            Exit;

         else

            Buffer.Add(Num);

            Num := Num + 1;

         end select;

      end loop;

   end Producer;

  

   task body Consumer is

      Num : Natural;

   begin

      loop

         select

            accept Stop;

            exit;

         else

            select

               Buffer.Get(Num);

               Put_Line("Consumed:" & Num'Image);

            else

               null;

            end select;

         end select;

      end loop;

   end Consumer;

             

begin

   delay 0.0001; -- wait 0.0001 second

   Producer.Stop;

   Consumer.Stop;

end Undersampling;


 15 Undersampling output
Consumed: 173

Consumed: 174

Consumed: 195

Consumed: 209

Consumed: 224

Consumed: 238

Consumed: 253

Consumed: 271

Consumed: 286

Consumed: 300

Consumed: 314

Consumed: 328

Consumed: 343

Consumed: 356

Consumed: 372

Consumed: 386

Consumed: 402

Consumed: 416

Consumed: 430

Consumed: 444

Consumed: 459

Consumed: 475

Consumed: 489

Consumed: 503

Consumed: 517

Consumed: 530

Consumed: 545


The main task calls the Stop entries for the producer and consumer to coordinate the shutdown of the program. Failure to do this will result in an eventual integer overflow and the corresponding run time exception.
It is also possible that the consumer may consume data faster than the data is produced. In this case either the consumer can be slowed down to wait for the producer, or the consumer can oversample the values. The following example demonstrates oversampling by the consumer.
16 Oversampling Example
with Ada.Text_IO; use Ada.Text_IO;


procedure Oversampling is

   protected Buffer is

      procedure Add(Item : Integer);

      function Get return Integer;

   private

      Value : Integer := Integer'First;

   end Buffer;


   protected body Buffer is

      procedure Add(Item : Integer) is

      begin

         Value := Item;

      end Add;


      function Get return Integer is

      begin

         return Value;

      end Get;

   end Buffer;


   task Producer is

      entry Stop;

   end Producer;


   task Consumer is

      entry Stop;

   end Consumer;


   task body Producer is

      Num : Natural := 0;

   begin

      Put_Line("Starting Producer");

      loop

         select

            accept Stop;

            exit;

         else

            Buffer.Add(Num);

            Num := Num + 1;

            delay 0.005;

         end select;

      end loop;

   end Producer;


   task body Consumer is

      Num : Natural;

   begin

      Put_Line("Starting Consumer");

      loop

         select

            accept Stop;

            exit;

         else

            Num := Buffer.Get;

            Put_Line("Consumed:" & Num'Image);

            Delay 0.001;

         end select;

      end loop;

   end consumer;


begin

   delay 0.1;

   Producer.Stop;

   Consumer.Stop;

end Oversampling;


17 Oversampling Output
Starting Producer

Starting Consumer

Consumed: 0

Consumed: 0

Consumed: 0

Consumed: 0

Consumed: 1

Consumed: 1

Consumed: 1

Consumed: 1

Consumed: 2

Consumed: 2

Consumed: 2

Consumed: 2

Consumed: 2

Consumed: 2

Consumed: 3

Consumed: 3

Consumed: 4

Consumed: 4

Consumed: 4

Consumed: 4

Consumed: 5

Consumed: 5

Consumed: 5

Consumed: 5

Consumed: 5

Consumed: 6

Consumed: 6

Consumed: 6

Consumed: 6

Consumed: 7

Consumed: 7

Consumed: 7

Consumed: 7

Consumed: 8

Consumed: 8

Consumed: 8

Consumed: 9

Consumed: 9

Consumed: 9

Consumed: 9

Consumed: 10

Consumed: 10

Consumed: 10

Consumed: 10

Consumed: 11

Consumed: 11

Consumed: 11

Consumed: 11

Consumed: 12

Consumed: 12

Consumed: 12

Consumed: 12

Consumed: 13

Consumed: 13

Consumed: 13

Consumed: 13

Consumed: 14

Consumed: 14

Consumed: 14

Consumed: 14

Consumed: 15

Consumed: 15

Consumed: 15

Consumed: 16

Consumed: 16

Consumed: 16

Consumed: 16

Consumed: 17

Consumed: 17

Consumed: 17


  • 9 October 2018 at 01:36

Ada Pre and Post Conditions


Ada preconditions and post-conditions are implemented using aspect clauses. While aspect clauses can include many other terms used to specify program behavior, this posting will focus on preconditions and post-conditions.


A thorough discussion of preconditions and post-conditions can be found at http://www.ada-auth.org/standards/12rat/html/Rat12-2-3.html

Since its first official version in 1983 the Ada language has always allowed the programmer to define data types and subtypes with specific ranges. For example:

type Byte is range -2**7..2**7 – 1;         -- signed integer type

type Unsigned_Byte is mod 2**8;             -- modular type

type Normalized is digits 5 range 0.0..1.0; -- floating point type

type Money is digits 10 delta 0.01;         -- decimal fixed point type

subtype Uppers is Character range ‘A’..’Z’; -- character subtype

subtype Positive is Integer range 1..Integer’Last;

type Days is (Mon, Tue, Wed, Thu, Fri, Sat, Sun);


When a defined type or subtype is used as a function or procedure parameter that type or subtype acts as a pre-condition on the call of the function or procedure.

procedure Swap(Left, Right : in out Positive);


The procedure example above defines a procedure named  Swap. The procedure takes two parameters of the subtype Positive. Even though subtype Positive is a subtype of Integer and every instance of Positive is also an instance of Integer, not every instance of Integer is an instance of Positive.  The Integer type can represent values less than 1, while the subtype Positive cannot. The Ada compiler writes run-time checks to ensure that the values passed to Swap are integers within the range of subtype Positive, creating a precondition for the procedure that all values passed to it must be Integer values greater than 0.

This use of strong typing in parameter calls provides some very limited precondition capability. A more robust precondition capability combined with a post-condition capability was introduced in the Ada 2012 standard.

Preconditions provide a guarantee to the writer of the function or procedure that a condition will be true when the program is called. Post-conditions provide a guarantee to the caller of the function or procedure that a condition will be true when the function or procedure call returns.

The precondition becomes a contract between the caller and the called subprogram when the subprogram is called. The post-condition becomes a contract between the caller and the called subprogram when the called subprogram returns. These contracts are direct implementations of the requirements for the called subprogram.

Stack Example


The following example shows a stack implementation which includes definition of some preconditions and some post-conditions.

-----------------------------------------------------------------------

-- Package implementing a generic bounded stack

-----------------------------------------------------------------------

Generic


   type Element_Type is private;


   with function Image(E : Element_Type) return String;


package Bounded_Stack is


   type Stack(Size : Positive) is tagged private;


   function Is_Full(S : in Stack) return Boolean;


   function Is_Empty(S : in Stack) return Boolean;


   procedure Push(S : in out Stack; Item : in Element_Type) with

     Pre => not S.Is_Full,

     Post => not S.Is_Empty;


   procedure Pop(S : in out Stack; Item : out Element_Type) with

     Pre => not S.Is_Empty,

     Post => not S.Is_Full;


   procedure Display(S : in Stack);


private


   type Buf is array(Positive range <>) of Element_Type;


   type Stack(Size : Positive) is tagged record

      Stk   : Buf(1..Size);

      Top   : Positive := 1;

      Count : Natural := 0;

   end record;


end Bounded_Stack;


This package specification defines the public interface and the private data definitions for a generic bounded stack ADT.  A bounded stack is created with a fixed maximum size.

The procedure Push pushes an item onto the stack. The precondition for Push requires the Stack parameter S to not be full (not S.Is_Full). The post-condition requires that after the successful Push operation the stack will not be empty (not S.Is_Empty). The Pop procedure has inverse requirements. One can only Pop a value from the stack if the stack is not empty before the procedure Pop is called (not S.Is_Empty). After a successful Pop operation the stack will not be full (not S.Is_Full).

The precondition and the post-condition seem nice enough, but how do they help the programmer develop correct code? Let’s look first at the implementation of the subprograms for the generic bounded stack and then at the “main” procedure used to test this stack ADT.

with Ada.Text_IO; use Ada.Text_IO;


package body Bounded_Stack is


   -------------

   -- Is_Full --

   -------------


   function Is_Full (S : in Stack) return Boolean is

   begin

      return S.Count = S.Size;

   end Is_Full;


   --------------

   -- Is_Empty --

   --------------


   function Is_Empty (S : in Stack) return Boolean is

   begin

      return S.Count = 0;

   end Is_Empty;


   ----------

   -- Push --

   ----------


   procedure Push

     (S : in out Stack; Item : in Element_Type) is

   begin

      S.Stk(S.Top) := Item;

      S.Top := S.Top + 1;

      S.Count := S.Count + 1;

   end Push;


   ---------

   -- Pop --

   ---------


   procedure Pop

     (S : in out Stack; Item : out Element_Type) is

   begin

      S.Top := S.Top - 1;

      Item := S.Stk(S.Top);

      S.Count := S.Count - 1;

   end Pop;


   -------------

   -- Display --

   -------------


   procedure Display (S : in Stack) is

   begin

      if S.Is_Empty then

         Put_Line("Stack is empty.");

      else

         for index in reverse 1..S.Top - 1 loop

            Put_Line(Image(S.Stk(Index)));

         end loop;

      end if;

      New_Line;

   end Display;


end Bounded_Stack;


Now, let’s focus on the Push and Pop procedures, since their specifications include preconditions and post-conditions.

   ----------

   -- Push --

   ----------


   procedure Push

     (S : in out Stack; Item : in Element_Type) is

   begin

      S.Stk(S.Top) := Item;

      S.Top := S.Top + 1;

      S.Count := S.Count + 1;

   end Push;


   ---------

   -- Pop --

   ---------


   procedure Pop

     (S : in out Stack; Item : out Element_Type) is

   begin

      S.Top := S.Top - 1;

      Item := S.Stk(S.Top);

      S.Count := S.Count - 1;

   end Pop;


Since the precondition for Pop guarantees that the stack is not full when this procedure is called there is no need to check for a stack-full condition within the procedure. Similarly there is no need for the Pop procedure to check if the stack is empty. The precondition for Pop guarantees that the stack is not empty when Pop is successfully called.

The programmer can simply assume the preconditions are satisfied while writing the code for a subprogram with preconditions.

Now, let’s look at the “main” procedure used to test this ADT:

with Ada.Text_IO; use Ada.Text_IO;

with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

with bounded_Stack;



procedure Main is

   type Options is (Push, Pop, Display, Quit);


   package Int_Stack is new bounded_Stack(Integer, Integer'Image);

   use Int_Stack;


   S : Stack(5);


   function Menu return Options is

      package Opts_Io is new Ada.Text_IO.Enumeration_IO(Options);

      use Opts_Io;

      Value : Options;

   begin

      Put_Line("-----------------------------------");

      Put_Line("    Push");

      Put_Line("    Pop");

      Put_Line("    Display");

      Put_Line("    Quit");

      Put_Line("-----------------------------------");

      Put_Line("Enter your choice");

      Get(Value);

      return Value;

   end Menu;


   Choice : Options;

   New_Value : Integer;

   Popped_Value : Integer;


begin

   loop

      Choice := Menu;

      case Choice is

         when Push =>

            Put("Enter the new value to push on the stack: ");

            Get(New_Value);

            S.Push(New_Value);

         when Pop =>

            S.Pop(Popped_Value);

            Put_Line("Popped " & Popped_VAlue'Image);

         when Display =>

            Put_Line("Stack contents:");

            S.Display;

         when Quit =>

            exit;

      end case;

   end loop;

end Main;


This test makes an instance of the Bounded_Stack package passing in the type Integer and the function Integer’Image. This creates a stack package containing Integer elements. The variable S is defined to be an instance of Stack from that package. This instance is set to contain a capacity of 5 elements.

S : Stack(5);


A function is defined to display and manipulate a text menu for interacting with the stack.  The function returns the value of type Options input by the user. The executable part of the Main procedure simply loops through calling the Menu function and handling the return value of that function until the Quit option is chosen.

The following output shows what happens when the first option chosen is to Pop a value from the stack. In this case the stack is still empty because no value has first been pushed onto the stack.

-----------------------------------

    Push

    Pop

    Display

    Quit

-----------------------------------

Enter your choice

Pop


raised SYSTEM.ASSERTIONS.ASSERT_FAILURE : failed precondition from bounded_stack.ads:16 instantiated at main.adb:7

[2018-10-20 09:23:58] process exited with status 1, elapsed time: 06.38s


Notice that the program immediately terminated due to the exception SYSTEM.ASSERTIONS.ASSERT_FAILURE. Furthermore, the exception was raised because the precondition stated in the file bounded_stack.ads, line 16 was violated for the instance of Bounded_Stack instantiated at line 7 of the file main.adb.


Line 16 of bounded_stack.ads is the line containing the precondition for the Pop operation.
Now let’s look at the behavior of pushing too many items onto the stack:

-----------------------------------

    Push

    Pop

    Display

    Quit

-----------------------------------

Enter your choice

push

Enter the new value to push on the stack: 1

-----------------------------------

    Push

    Pop

    Display

    Quit

-----------------------------------

Enter your choice

push

Enter the new value to push on the stack: 2

-----------------------------------

    Push

    Pop

    Display

    Quit

-----------------------------------

Enter your choice

push

Enter the new value to push on the stack: 3

-----------------------------------

    Push

    Pop

    Display

    Quit

-----------------------------------

Enter your choice

push

Enter the new value to push on the stack: 4

-----------------------------------

    Push

    Pop

    Display

    Quit

-----------------------------------

Enter your choice

push

Enter the new value to push on the stack: 5

-----------------------------------

    Push

    Pop

    Display

    Quit

-----------------------------------

Enter your choice

display

Stack contents:

 5

 4

 3

 2

 1


-----------------------------------

    Push

    Pop

    Display

    Quit

-----------------------------------

Enter your choice

push

Enter the new value to push on the stack: 6



raised SYSTEM.ASSERTIONS.ASSERT_FAILURE : failed precondition from bounded_stack.ads:13 instantiated at main.adb:7

[2018-10-20 09:44:39] process exited with status 1, elapsed time: 38.56s


Again, the exception SYSTEM.ASSERTIONS.ASSERT_FAILURE was raised, this time the precondition for the Push operation was violated.


In both cases the program was terminated because the precondition for a procedure call was violated. The preconditions prevented buffer overflow errors while ensuring the requirements for the Push and Pop procedures.


  • 20 October 2018 at 16:02

Comparison of Simple Matrix Code in C and Ada


Summary

A matrix is a two dimensional array of elements. Matrices are commonly used to represent spread sheets or tables of information. A square matrix is a matrix with the same number of rows and columns. A square matrix is said to be symmetric if the transpose of the matrix is equal to the original matrix. Only square matrices can be symmetric.

The website https://www.studytonight.com/c/programs/array/check-square-matrix-is-symmetric-or-not provides an example of a C program to determine if a square matrix input by the user is symmetric or not. The source code for the C program can be viewed through the link shown above.

Remarks concerning the C code


The code works very well within limits. The limits of its proper behavior are defined by the declaration of the arrays found on line 7 of the C source code.

int c, d, a[10][10], b[10][10], n, temp;


Two matrices are declared. Both matrices have a dimension of 10. While 10 may be a useful arbitrary dimension for an example, this approach exposes the program to possible buffer overflow. The obvious way to avoid such buffer overflow in C is to dynamically allocate the two matrices after inputting the matrix dimension from the user. The downside of using dynamically allocated matrices is the need to also explicitly use pointers. Specifically, the line quoted above would need to be changed to the following.

int c, d, **a, **b, n, temp;


This section of the StudyTonight web site is trying to concentrate on arrays without exposing their relationship to pointers in C.
Similarly, I suspect the example does not define a function to display the matrices because of the need to pass the matrix as an int **.

Functionally comparable Ada code


Following is Ada code which performs the same actions as the C code while avoiding any exposure to buffer overflow and also avoiding complicated pointer notation.
Ada arrays are first class types. This is true no matter how many dimensions an array contains.

-----------------------------------------------------------------------

-- Square Matrix Symmetry

-----------------------------------------------------------------------

with Ada.Text_IO; use Ada.Text_IO;

with Ada.Integer_Text_IO; use Ada.Integer_Text_Io;


procedure Symmetric_Matrix is


   type Matrix is array(Positive range <>, Positive range <>) of Integer;

  

   procedure Print(M : Matrix) is

   begin

      for Row in M'Range(1) loop

         for Col in M'Range(2) loop

            Put(M(Row, Col)'Image & " ");

         end loop;

         New_Line;

      end loop;

   end Print;

  

   Dimension : Positive;

begin

   Put_Line("Enter the dimension of the matrix: ");

   Get(Dimension);

   declare

      A : Matrix(1..Dimension, 1..Dimension);

      B : Matrix(1..Dimension, 1..Dimension);

   begin

      Put_Line("Enter the" & Positive'Image(Dimension * Dimension) &

                 " elements of the matrix:");

      for Value of A loop

         Get(Value);

      end loop;

     

      -- find the transpose of A and store it in B

     

      for Row in A'Range(1) loop

         for Col in A'Range(2) loop

            B(Col, Row) := A(Row, Col);

         end loop;

      end loop;

     

      -- print matrix A

      New_Line;

      Put_Line("The original matrix is:");

     

      Print(A);

     

      -- print the transpose of A

      New_Line;

      Put_Line("The transpose matrix is:");

      Print(B);

     

      -- Checking if the original matrix is the same as its transpose

     

      if A = B then

         Put_Line("The matrix is symmetric.");

      else

         Put_Line("The matrix is not symmetric.");

      end if;

   end;

end Symmetric_Matrix;

Remarks concerning the Ada code


A Matrix type is defined as an unconstrained two dimensional array with each index of the subtype Positive. Each element of type Matrix is an Integer. Use of an unconstrained array type allows the program to create instances of Matrix containing exactly the number of elements specified by the user input.

Every Ada array has several attributes which are always available to the programmer. This program uses the ‘Range attribute which evaluates to the range of index values for the array. Since Matrix is a two-dimensional array there are two range attributes. ‘Range(1) evaluates to the index values for the first dimension of the array. ‘Range(2) evaluates to the index values for the second dimension of the array. The procedure Print has a single parameter M, which is an instance of type Matrix. Since Matrix is an unconstrained type the parameter M may correspond to any instance of Matrix, no matter what the sizes of the dimensions may be.

The “declare” block inside the Ada program allows the definition of Matrix A and Matrix B according to the user input all allocated from the program stack rather than from the heap, thus avoiding the use of pointers or their Ada analogs called access types.
The input loop used to fill Matrix A is an iterator loop. The loop parameter Value references each value in Matrix A in sequence.

Ada implicitly provides equality testing for array instances. In this program we simply compare Matrix A with Matrix B. If they are equal then Matrix A is symmetric, otherwise Matrix A is not symmetric.

The output of a representative execution of this program is:

Enter the dimension of the matrix:

3

Enter the 9 elements of the matrix:

1 7 3 7 5 -5 3 -5 6


The original matrix is:

 1  7  3

 7  5 -5

 3 -5  6


The transpose matrix is:

 1  7  3

 7  5 -5

 3 -5  6

The matrix is symmetric.

The Ada solution is somewhat simpler than the C solution while also being more secure. If the user chooses to specify a matrix with a dimension greater than 10 using the C version the variable ‘n’ will be overwritten with possibly catastrophic effects on the correctness of the program. The Ada program suffers no such security flaws.

  • 20 November 2018 at 03:14

Ada Loop Tutorial


The Ada programming language provides four different looping constructs; the “for” loop, the “while” loop, the simple loop and the container iterator loop.

The For loop

The Ada “for” loop iterates over a range of discrete values. Ada discrete values include signed integer values, modular (unsigned) integer values, and enumeration values. Ada ranges are always specified as lowest_value..highest value. Every “for” loop has a loop variable which is NOT declared before it is used. The loop variable has the type of the loop range. The loop variable cannot be altered within the body of the loop.

Ranges are always specified as lowest_value..highest_value, even if one wants to iterate through the range in reverse order. Any range where the first value is greater than the last value is considered an empty range. To iterate through a range in reverse order simply add the “reverse” reserved word

For Num in reverse 1..10 loop


Example For loop

with Ada.Text_Io; use Ada.Text_IO;

with Ada.Calendar; use Ada.Calendar;


procedure For_Loop is

   C     : Positive;

   start : time;

   Stop  : Time;

begin

   Start := Clock;

   for I in 1..10**9 loop

      C := I;

   end loop;

   Stop := Clock;

   Put_Line("counted " & C'Image & " numbers in " & Duration'Image(Stop - Start)

            & " seconds");

end For_Loop;

The loop variable in this example is named “I”. The first iteration of the loop variable I contains the value 1. The second iteration I contains 2. The final iteration of the loop I contains 10**9 (1000000000).

The output of the program is

counted  1000000000 numbers in  0.577727576 seconds


The While loop

The Ada while loop iterates while the condition specified at the top of the loop is true. The condition is always evaluated before the start of an iteration. The condition must evaluate to a Boolean value (False or True). Ada Boolean values are an enumeration type, not a numeric type. Ada does NOT provide any implicit conversion between zero and False or between not-zero and True as is done in the C language.

Example While loop

with Ada.Text_IO; use Ada.Text_IO;

with Ada.Calendar; use Ada.Calendar;


procedure While_Loop is

   Start : Time;

   Stop  : Time;

   C     : Positive := 1;

begin

   Start := Clock;

   while C < 10**9 loop

      C := C + 1;

   end loop;

   Stop := Clock;

   Put_Line("counted " & C'Image & " numbers in " & Duration'Image(Stop - Start)

              & " seconds");

end While_Loop;

Unlike C, C++ or Java, the Boolean expression in Ada does not need to be enclosed in parentheses “()”.

The output of the program is

counted  1000000000 numbers in  0.559296318 seconds


The Simple loop

The Ada simple loop continues to iterate until an exit is executed within the body of the loop. If the body of the loop does not contain an exit command the loop behaves as an infinite loop. The exit command is typically found within a conditional expression such as

if C = 10**9 then

   exit;

end if;


The expression above can be abbreviated to

exit when C = 10**9;


Example Simple loop

with Ada.Text_IO; use Ada.Text_IO;

with Ada.Calendar; use Ada.Calendar;


procedure Simple_Loop is

   Start : Time;

   Stop  : Time;

   C     : Positive := 1;

begin

   Start := Clock;

   loop

      C := C + 1;

      exit when C = 10**9;

   end loop;

   Stop := Clock;

   Put_Line("counted " & C'Image & " numbers in " & Duration'Image(Stop - Start)

              & " seconds");

end Simple_Loop;

The output of the program is

counted  1000000000 numbers in  0.576855340 seconds


The Iterator loop

The iterator loop is used to iterate through containers, and may be used with arrays. The iterator loop traverses the values of the container object or array object allowing reading or manipulation of each value in sequence.

The iterator loop strongly resembles the “for” loop with some subtle differences. The “loop variable” is actually a member of the container or array. No range is specified. The iteration proceeds through each data element in the container or array.

Example Iterator loop


The following example dynamically allocates an array of 10**9 elements. Dynamic memory allocation is used because such an array is too large to fit on the program stack.

with Ada.Text_Io; use Ada.Text_IO;

with Ada.Calendar; use Ada.Calendar;


procedure Iterator_Loop is

   type Nums_Array is Array(1..10**9) of Integer;

   type Nums_Access is access Nums_Array;

   Nums : Nums_Access := new Nums_Array;

   Start, Stop : Time;

   Counter : Integer := 1;

begin

   Start := Clock;

   for Value of Nums.all loop

      Value := Counter;

      Counter := Counter + 1;

   end loop;

   Stop := Clock;

   Put_Line("counted " & Nums(Nums'Last)'Image & " numbers in " &

            Duration'Image(Stop - Start) & " seconds");

end Iterator_Loop;

The variable Nums is defined to be an access type which references an instance of Nums_Array. The syntax Nums.all evaluates to the array referenced by Nums.

The output of the program is

counted  1000000000 numbers in  3.208108244 seconds


As you can see, iterator loops are much slower than the other loop constructs.

  • 22 November 2018 at 20:18

Security Snippet Number 1


Common Weakness Enumerations can be found at https://cwe.mitre.org/data/definitions/699.html

CWE-129: Improper Validation of Array Index

Description

The product uses untrusted input when calculating or using an array index, but the product does not validate or incorrectly validates the index to ensure the index references a valid position within the array.

Demonstrative Examples

Example 1 : Java language

public String getValue(int index) {

  return array[index];

}

This may result in ArrayIndexOutOfBounds Exception being raised if index is outside the range of the array.

Example 2: Java language

private void buildList (int untrustedListSize) {

  if ( 0 > untrustedListSize ) {

    die(“Negative value supplied for list size, die evil hacker!”);

  }

  Widget[] list = new Widget[ untrustedListSize ];

  list[0] = new Widget()’

}

This example attempts to build a list from a user-specified value, and even checks to ensure a non-negative value is supplied. If, however, a 0 value is provided, the code will build an array of size 0 and then try to store a new Widget in the first location, causing an exception to be thrown.

Example 3: C language

int getValueFromArray(int *array, int len, int index) {


int value;


// check that the array index is less than the maximum


// length of the array

if (index < len) {


// get the value at the specified index of the array

value = array[index];

}

// if array index is invalid then output error message


// and return value indicating error

else {

printf("Value is: %d\n", array[index]);

value = -1;

}


return value;

}
This method only verifies that the given array index is less than the maximum length of the array but does not check for the minimum value (CWE-839). This will allow a negative value to be accepted as the input array index, which will result in a out of bounds read (CWE-125) and may allow access to sensitive memory. The input array index should be checked to verify that is within the maximum and minimum range required for the array (CWE-129).

Example 4: C language

int main (int argc, char **argv) {

  char *items[] = {"boat", "car", "truck", "train"};

  int index = GetUntrustedOffset();

  printf("You selected %s\n", items[index-1]);

}
The programmer allows the user to specify which element in the list to select, however an attacker can provide an out-of-bounds offset, resulting in a buffer over-read (CWE-126).

Potential Mitigations using the Ada language

Ada allows the programmer to define numeric types and subtypes with a specified range of valid values. All instances of a specified numeric type must all satisfy the range validity requirements specified for that type.
Example 3 above defines a function returning an int. The function has two parameters, a pointer to an int, which is intended to point to an array of ints, and an int index value which is intended to specify an index in the array.
Within the function a check is made for an invalid index value and the int value -1 is returned if an error is detected. Nowhere in the description of the array or the function is there any restriction on the range of values any of these ints can contain. For instance, it is completely valid for the array to contain negative values, including -1. If -1 is also used to indicate an error then the program can never distinguish between an error and an array value of -1. Much must be changed to fix this fault.

Mitigation 1:

function getIndex(index : Positive) return String with

   Pre => index in String_Array’Range;


function getIndex(index : Positive) return String is

begin

   return String_Array(index);

end getIndex;

The function specification establishes a pre-condition requiring the parameter index to be within the range of index values associated with String_Array. The pre-condition assures that the index value used within the getIndex function is a valid index of String_Array.

Mitigation 2:

generic

   type Element_Type is private;

package List_Handler is

   type List(Size : Positive) is tagged private;

private

   type Element_Array is array (Positive range <>) of Element_Type;

   type List(Size : Positive) is tagged record

      Buffer : Element_Array(1..Size);

   end record;

end List_Handler;

The package specification defines a generic package that can be instantiated with any type, creating a “List” of that type. The type List is defined with a discriminant value named Size which determines the size of the array to be used for a particular instance of List. The discriminant Size is required to be a value of the pre-defined subtype Positive. The minimum valid value for an instance of Positive is 1. This prevents the size of the Buffer element in List from being either negative or 0.

Mitigation 3

type Int_Array is array(Natural range <>) of Integer;

...

function getValueFromArray(Nums : Int_Array; index : Natural) return Integer

   with Pre => index in Nums’Range;


function getValueFromArray(Nums : Int_Array; index : Natural) return Integer is

begin

   return Nums(index);

end getValueFromArray;

The type Int_Array is defined as being indexed by a value of the pre-defined subtype Natural. The minimum value of an instance of Natural is 0. The pre-condition assures that the value of index is within the valid index values for any instance of Int_Array passed to the function. The function getValueFromArray always returns a valid array element when the pre-condition is satisfied. If the pre-condition is not satisfied the call results in an Assertion_Error exception being raised.

Mitigation 3 alternate

This mitigation fixes the indexing problems without the use of pre-conditions.
type Result_Type(Is_Valid : Boolean) is record

   case Is_Valid is

      when True => Value : Integer;

      when False => null;

   end case;

end record;


type Int_Array is array(Natural range <>) of Integer;


function getValueFromArray(Nums : Int_Array; Index : Natural)

                                         return Result_Type is

begin

   if Index in Nums’Range then

      return (Is_Valid => True, Value => Nums(Index));

   else

      return (Is_Valid => False);

   end if;

end getValueFromArray;

The type Result_Type is defined as a variant record. When the discriminant, named Is_Valid, is True the type contains two fields; Is_Valid and Value. When the discriminant Is_Valid is False the type only contains the field Is_Valid.
The conditional expression tests the parameter Index. If Index contains a value within the valid range of index values for the array Nums then the function returns an instance of Result_Type with the field Is_Valid containing True and the field Value containing the integer value that was in Nums(Index), otherwise the function returns an instance of Result_Type only containing the field Is_Valid and that field contains a False value.
This solution clearly separates the concept of an erroneous index value from any value contained in the array to be searched. Use of the variant record allows the function to return two values at the same time. No Value field is returned when the index is invalid because to do so would suggest that a valid value was found. This solution prevents a programmer from erroneously using an invalid return value.

Mitigation 4

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

with Ada.Integer_IO; use Ada.Integer_IO;


procedure main is

   subtype Item_Index is Integer range 1..4;

   type Item_List is array(Index_Type) of Unbounded_String;

  

   function getOffset return Item_Index is

      Offset : Integer;

   begin

      loop

         Put(“Enter a number in the range of 1 through 4: “);

         Get(Offset);

         exit when Offset in Item_Index;

      end loop;

      return Offset;

   end getOffset;


   Items : Item_List := (To_Unbounded_String(“boat”),

                         To_Unbounded_String(“car”),

                         To_Unbounded_String(“truck”),

                         To_Unbounded_String(“train”));

   index : Item_Index := getOffset;

begin

   Put_Line(“You selected “ & To_String(Item_List(index)));

end main;

The programmer allows the user to specify the element to select and the selection will only access the array after a valid index value has been chosen.
The function getOffset takes no parameters and returns a value of the subtype Item_Index. The simple loop statement continually prompts the user until the user enters a valid value. The function then returns that value. The array of Unbounded_String is only accessed after a valid index value has been obtained from the user.

Conclusion

These examples show that CWE-129 Improper Validation of Array Index is far simpler to avoid using Ada than using C, C++, or Java.

  • 30 January 2019 at 00:10

Security Snippet Number 2

This security snippet deals with CWE-190 “Integer Overflow or Wraparound” described in https://cwe.mitre.org/data/definitions/190.html
The problem description for this weakness enumeration states:
An integer overflow or wraparound occurs when an integer value is incremented to a value that is too large to store in the associated representation. When this occurs, the value may wrap to become a very small or negative number. While this may be intended behavior in circumstances that rely on wrapping, it can have security consequences if the wrap is unexpected. This is especially the case if the integer overflow can be triggered using user-supplied inputs. This becomes security-critical when the result is used to control looping, make a security decision, or determine the offset or size in behaviors such as memory allocation, copying, concatenation, etc.
This problem is prevalent in languages exhibiting structural type definitions rather than nominative type definitions for numeric types[1]. Languages such as C which use structural type definitions provide implicit type conversions between numeric types. Furthermore, structural type definitions of numeric types do not allow the programmer to define a type with a programmer-defined range of valid values. Integer types, for instance, generally come in 8 bit, 16 bit, 32 bit and 64 bit types. There are no provisions in C to define an 8 bit type with a range of -10 through 10. The closest one can do is use a signed char type which provides a range of values from -128 through 127. Furthermore, since C uses only structural information to determine a numeric type integer types experience wrap-around when overflowing or underflowing values.
Using nominative types for numeric data types also allows the specification of a particular data range for a type. Thus, two different types using the same bit representation, such as 8 bits, can be kept separate.
The following example of C code is given in CWE-190 as an example of wrap-around issues:
#define JAN 1

#define FEB 2

#define MAR 3


short getMonthlySales(int month) {...}


float calculateRevenueForQuarter(short quarterSold) {...}


int determineFirstQuarterRevenue() {


// Variable for sales revenue for the quarter

float quarterRevenue = 0.0f;


short JanSold = getMonthlySales(JAN); /* Get sales in January */

short FebSold = getMonthlySales(FEB); /* Get sales in February */

short MarSold = getMonthlySales(MAR); /* Get sales in March */


// Calculate quarterly total

short quarterSold = JanSold + FebSold + MarSold;


// Calculate the total revenue for the quarter

quarterRevenue = calculateRevenueForQuarter(quarterSold);


saveFirstQuarterRevenue(quarterRevenue);


return 0;

}

This code actually exhibits many faults, as well as a potential wrap-around.
·         The macros defining JAN, FEB, and MAR evaluate to integer values which are interpreted as month numbers by the function getMonthlySales. There is no assurance that a value greater than 12 cannot be passed to the function, resulting in erroneous behavior.
·         Each month’s sales are limited to a maximum of 32767 which may be too small for the monthly revenue for a business. Furthermore, the total of three months sales numbers is also restricted to a maximum of 32767.
A more correct implementation using Ada, which provides nominative numeric types is:
type Months is (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);


type Sales_Count is range 0..2**31;


function getMonthlySales(Month : Months)return Sales_Count;


function calculateRevenueForQuarter(Count : Sales_Count) return float;


procedure determineFirstQuarterRevenue is


   JanSold : Sales_Count := getMonthlySales(Jan);

   FebSold : Sales_Count := getMonthlySales(Feb);

   MarSold : Sales_Count := getMonthlySales(Mar);


   Quarter_Sold : Sales_Count;


begin


   Quarter_Sold := JanSold + FebSold + MarSold;


   saveFirstQuarterRevenue(calulateRevenueForQuarter(Quarter_Sold));


end determineFirstQuarterRevenue;


The Ada version implementation shown above defines values for all months and requires the function getMonthlySales to use a parameter of the enumeration type Months, not simply an integer. The C version allows any int value to be passed to the function.
The type Sales_Count is defined to hold any value from 0 through 2^31 (2,147,483,648). Note that this also ensures that a negative sales count cannot be reported. The type Sales_Count will not exhibit wrap-around.
Use of enumerations in C will not provide the same benefits as the Ada enumerated type in the example above. Ada enumerated types are a separate type not implicitly convertible to an integer type, while C enums are simply aliases for int values. The parameter type for the function must still be an int, which does not restrict the parameter values to a set of valid values.
  • 2 February 2019 at 00:59

Stack Abstract Data Type using Ada


The stack Abstract Data Type is one of the simplest data types to create and understand. There are fundamentally two kinds of stack types.
·         bounded stacks which have a pre-determined capacity
·         unbounded stacks which can grow to the limits of computer memory.
Every stack implementation has some common subprograms.
  • ·         Is_Empty – This function returns true if the stack is empty and false if it is not empty
  • ·         Size – A function returning number of elements currently contained by the stack
  • ·         Top – A function returning the top element of a stack that is not empty
  • ·         Push – Push a data element onto the stack
  • ·         Pop – A procedure that pops the top element off the stack, passing the popped element out as a parameter
  • ·         Clear – A procedure that empties the stack
  • ·         Display – A procedure that displays the current contents of the stack

A bounded stack may also have the function Is_Full, which returns true if the stack is full and returns false if it is not full. Notice that Is_Empty is not simply the inverse of Is_Full. A bounded stack with a capacity of 10 elements may contain 0 elements, in which case it is empty, or it may contain some number of elements from 1 through 9, in which case it is neither empty nor full, or it may contain 10 elements, in which case it is full.

Unbounded Stack

Unbounded stacks are commonly implemented as specialized linked lists. Every Push operation results in the dynamic allocation of a new item onto the stack. Every Pop operation results in one item from the stack being freed back to the program heap.
The following implementation of an unbounded stack is defined in an Ada generic package, allowing instances of the unbounded stack to be created containing any data type. 
Ada does provide a pre-defined doubly linked list package as part of its standard container library, but this example re-invents a simple singly linked list to give a complete example of how the unbounded stack can be implemented.

The package specification defines the API for the stack abstract data type:

generic

   type Element_Type is private;

  

   with function Image (Item : in Element_Type) return String;

  

package Unbounded_Stack is

   type Stack is tagged private;


   function Is_Empty (S : in Stack) return Boolean;

  

   function Size (S : in Stack) return Natural;

  

   function Top (S : in Stack) return Element_Type with

     Pre => not S.Is_Empty;

  

   procedure Push (S : in out Stack; Value : in Element_Type) with

     Post => S.Size = S'Old.Size + 1;

  

   procedure Pop (S : in out Stack; Value : out Element_Type) with

      Pre  => not S.Is_Empty,

     Post => S.Size = S'Old.Size - 1;

  

   procedure Clear (S : in out Stack) with

     Post => S.Is_Empty;

  

   procedure Display (S : Stack);

  

private

  

   type Cell;

  

   type Cell_Access is access Cell;

  

   type Cell is record

      Value : Element_Type;

      Next  : Cell_Access;

   end record;


   type Stack is tagged record

      Head  : Cell_Access := null;

      Count : Natural     := 0;

   end record;


end Unbounded_Stack;


The package specification defines two generic parameters which must be specified when an instance of the package is created. The first generic parameter is the data type that the stack will contain. The second generic parameter is a function taking an instance of the stack element type and returning a string representation of the value of that data type.
Several of the functions and procedures have preconditions and/or post-conditions associated with them. These pre and post conditions specify the requirements of those functions and procedures.

Function or Procedure Name
Condition
Description
Top
Pre => not S.Is_Empty
This precondition requires the stack to not be empty before calling the Top function.
Push
Post => S.Size = S'Old.Size + 1
Upon completion of this procedure the size of the stack must increase by 1.
Pop
Pre  => not S.Is_Empty,

Post => S.Size = S'Old.Size - 1
This procedure cannot be called when the stack is empty. Upon completion of this procedure the size of the stack must decrease by 1.
Clear
Post => S.Is_Empty
Upon completion of this procedure the stack will be empty.

The private part of the package specification defines the data types needed to implement a linked list.
The package body contains the implementation of all the functions and procedures defined in the package specification.

with Ada.Unchecked_Deallocation;

with Ada.Text_IO; use Ada.Text_IO;


package body Unbounded_Stack is

   procedure free is new Ada.Unchecked_Deallocation (Cell, Cell_Access);


   --------------

   -- Is_Empty --

   --------------


   function Is_Empty (S : in Stack) return Boolean is

   begin

      return S.Count = 0;

   end Is_Empty;


   ----------

   -- Size --

   ----------


   function Size (S : in Stack) return Natural is

   begin

      return S.Count;

   end Size;


   ---------

   -- Top --

   ---------


   function Top (S : in Stack) return Element_Type is

   begin

      return S.Head.Value;

   end Top;


   ----------

   -- Push --

   ----------


   procedure Push (S : in out Stack; Value : in Element_Type) is

      C : Cell_Access := new Cell;

   begin

      C.Value := Value;

      C.Next  := S.Head;

      S.Head  := C;

      S.Count := S.Count + 1;

   end Push;


   ---------

   -- Pop --

   ---------


   procedure Pop (S : in out Stack; Value : out Element_Type) is

      C : Cell_Access := S.Head;

   begin

      Value   := S.Head.Value;

      S.Head  := S.Head.Next;

      S.Count := S.Count - 1;

      free (C);

   end Pop;


   -----------

   -- Clear --

   -----------


   procedure Clear (S : in out Stack) is

      C : Cell_Access := S.Head;

   begin

      while S.Head /= null loop

         C       := S.Head;

         S.Head  := S.Head.Next;

         S.Count := S.Count - 1;

         free (C);

      end loop;

   end Clear;


   -------------

   -- Display --

   -------------


   procedure Display (S : Stack) is

      C : Cell_Access := S.Head;

   begin

      if S.Is_Empty then

         Put_Line ("The stack is empty.");

      end if;


      for I in 1 .. S.Count loop

         Put_Line (Image (C.Value));

         C := C.Next;

      end loop;

   end Display;


end Unbounded_Stack;

Bounded Stack

The bounded stack is also implemented as a generic package. Instead of using a linked list which can be enlarged with each Push operation, the bounded stack uses an array defined when an instance of the bounded stack is declared.
You will also notice that the Is_Full function has been defined for the bounded stack. Additionally, the Push procedure now has a precondition requiring the stack not to be full when Push is called. The names and interfaces for all other functions and procedures are identical between the bounded stack and the unbounded stack.

generic

  

   type Element_Type is private;

  

   with function Image (Item : in Element_Type) return String;

  

package Bounded_Stack is

  

   type Stack (Size : Positive) is tagged private;


   function Is_Empty (S : in Stack) return Boolean;

  

   function Is_Full (S : in Stack) return Boolean;

  

   function Count (S : in Stack) return Natural;

  

   function Top (S : in Stack) return Element_Type with

     Pre => not S.Is_Empty;

  

   procedure Push (S : in out Stack; Value : in Element_Type) with

      Pre  => not S.Is_Full,

     Post => S.Count = S'Old.Count + 1;

  

   procedure Pop (S : in out Stack; Value : out Element_Type) with

      Pre  => not S.Is_Empty,

     Post => S.Count = S'Old.Count - 1;

  

   procedure Clear (S : in out Stack) with

     Post => S.Is_Empty;

  

   procedure Display (S : in Stack);

  

private

  

   type Buff_T is array (Positive range <>) of Element_Type;

  

   type Stack (Size : Positive) is tagged record

      Buff  : Buff_T (1 .. Size);

      Index : Positive := 1;

      Tally : Natural  := 0;

   end record;


end Bounded_Stack;

The implementation of the bounded stack differs from the unbounded stack because stack elements are never dynamically allocated or de-allocated.

with Ada.Text_IO; use Ada.Text_IO;


package body Bounded_Stack is


   --------------

   -- Is_Empty --

   --------------


   function Is_Empty (S : in Stack) return Boolean is

   begin

      return S.Tally = 0;

   end Is_Empty;


   -------------

   -- Is_Full --

   -------------


   function Is_Full (S : in Stack) return Boolean is

   begin

      return S.Tally = S.Size;

   end Is_Full;


   -----------

   -- Count --

   -----------


   function Count (S : in Stack) return Natural is

   begin

      return S.Tally;

   end Count;


   ---------

   -- Top --

   ---------


   function Top (S : in Stack) return Element_Type is

   begin

      return S.Buff(S.Index - 1);

   end Top;


   ----------

   -- Push --

   ----------


   procedure Push (S : in out Stack;  Value : in Element_Type) is

   begin

      S.Buff(S.Index) := Value;

      S.Tally         := S.Tally + 1;

      S.Index         := S.Index + 1;

   end Push;


   ---------

   -- Pop --

   ---------


   procedure Pop (S : in out Stack; Value : out Element_Type) is

   begin

      S.Tally := S.Tally - 1;

      S.Index := S.Index - 1;

      Value   := S.Buff(S.Index);

   end Pop;


   -----------

   -- Clear --

   -----------


   procedure Clear (S : in out Stack) is

   begin

      S.Tally := 0;

      S.Index := 1;

   end Clear;


   -------------

   -- Display --

   -------------


   procedure Display (S : in Stack) is

   begin

      if S.Tally = 0 then

         Put_Line("The stack is empty.");

      else

         for I in reverse 1..S.Index - 1 loop

            Put_Line(Image(S.Buff(I)));

         end loop;

      end if;

   end Display;


end Bounded_Stack;

Since the API for both the bounded and unbounded stack packages are so similar the use of these packages is very similar.

Using the unbounded stack package


with Ada.Text_IO; use Ada.Text_IO;

with Unbounded_Stack;


procedure Main is


   package Int_Stack is new Unbounded_Stack (Integer, Integer'Image);

   use Int_Stack;


   S : Stack;

   V : Integer;


begin

   Put_Line ("Push 5 values on the stack");


   for I in 1 .. 5 loop

      S.Push (I);

   end loop;

   S.Display;


   Put_Line ("Pop 2 values off of stack");


   for I in 1 .. 2 loop

      S.Pop (V);

      Put_Line ("Popped value" & V'Image);

   end loop;


   S.Display;

   Put_Line ("The top of the stack is" & S.Top'Image);

   S.Clear;

   S.Display;

end Main;

Using the bounded stack package


with Ada.Text_IO; use Ada.Text_IO;

with bounded_stack;


procedure Main is


   package Int_Stack is new bounded_stack (integer, integer'image);

   use Int_Stack;


   S : Stack (10);

   V : Integer;


begin


   Put_Line ("Push 5 values on the stack");


   for I in 1 .. 5 loop

      S.Push (I);

   end loop;


   S.Display;


   Put_Line ("Pop 2 values off of stack");


   for I in 1 .. 2 loop

      S.Pop (V);

      Put_Line ("Popped value" & V'Image);

   end loop;


   S.Display;


   Put_Line ("Push 5 values on the stack");


   for I in 1 .. 5 loop

      S.Push (I);

   end loop;


   S.Display;


   Put_Line ("The top of the stack is" & S.Top'Image);

   S.Clear;

   S.Display;

end Main;

In the bounded stack instance the variable S is declared to be a stack with a capacity of 10 elements. This is done in the line

S : Stack (10);


The (10) sets the Size parameter of the Stack record to 10 for this instance.

  • 5 February 2019 at 02:42

Parallel summation of a large array without shared data locks


The traditional producer/consumer pattern employs a shared buffer between the producer and the consumer.  Many producer/consumer problems are simply sequential problems with the overhead of multiple tasks and a shared buffer.
Parallel operations, on the other hand, are more naturally concurrent without the locking overhead of a shared buffer. Instead non-overlapping data elements of a collection such as an array are assigned to two or more tasks, and identical tasks process their subsets of the collection without need of locking the collection.

 If the parallel task is to sum all the elements in the array then task 1 in the diagram above will sum the elements in the first half of the array while task 2 sums the elements in the second half of the array. Task 1 and task 2 then simply report their subtotals to the parent task which adds the two values and returns the final total.
The following source code is an Ada package for parallel addition along with a procedure to test the package.

package Parallel_Addition is

   type Data_Array is array(Integer range <>) of Integer;

   type Data_Access is access all Data_Array;

   function Sum(Item : in not null Data_Access) return Integer;

end Parallel_Addition;


The package specification above defines an array type that can be used by the Sum function. The Sum function declares a parameter of the type Data_Accesss so that the function can handle arrays created either on the stack or on the heap.

package body Parallel_Addition is


   ---------

   -- Sum --

   ---------


   function Sum (Item : in not null Data_Access) return Integer is

      task type Adder is

         entry Set (Min : Integer; Max : Integer);

         entry Report (Value : out Integer);

      end Adder;


      task body Adder is

         Total : Integer := 0;

         First : Integer;

         Last  : Integer;

      begin

         accept Set (Min : Integer; Max : Integer) do

            First := Min;

            Last  := Max;

         end Set;

         for I in First .. Last loop

            Total := Total + Item (I);

         end loop;

         accept Report (Value : out Integer) do

            Value := Total;

         end Report;

      end Adder;

      A1  : Adder;

      A2  : Adder;

      R1  : Integer;

      R2  : Integer;

      Mid : constant Integer := (Item'Length / 2) + Item'First;

   begin

      A1.Set (Min => Item'First, Max => Mid);

      A2.Set (Min => Mid + 1, Max => Item'Last);

      A1.Report (R1);

      A2.Report (R2);

      return R1 + R2;

   end Sum;

end Parallel_Addition;


The package body for Parallel_Addition simply implements the Sum function. The Sum function defines a task type named Adder. That task type has two entries. The Set entry receives the minimum and maximum index values to be processed. The Report entry passes the locally calculated subtotal back to the Sum function. Each instance of Adder sums the values in the index range from Min to Max in the array passed as the Sum formal parameter Item, then passes the local sum back through the Report entry.
Two instances of Adder are created as well as two variables to contain results, one result for each Adder task. The variable Mid calculates the middle index value of the array Item.
Adder tasks A1 and A2 suspend at their Set entry until their Set entry is called. The then concurrently process the array slices indicated by their Min and Max values. They then suspend until their Report entry is called.
The Sum function simply calls the two Set entries and then calls the two Report entries. Finally Sum returns the sum of R1 and R2.
The test procedure for the Parallel_Addition package is:

with Parallel_Addition; use Parallel_Addition;

with Ada.Text_IO;       use Ada.Text_IO;

with Ada.Calendar;      use Ada.Calendar;


procedure Parallel_Addition_Test is

   The_Data : Data_Access := new Data_Array (1 .. Integer'Last);

   Start    : Time;

   Stop     : Time;

   The_Sum  : Integer;


begin

   The_Data.all := (others => 1);

   Start        := Clock;

   The_Sum      := Sum (The_Data);

   Stop         := Clock;

   Put_Line ("The sum is: " & Integer'Image (The_Sum));

   Put_Line

     ("Addition elapsed time is " &

      Duration'Image (Stop - Start) &

        " seconds.");

   Put_Line

     ("Time per addition operation is " &

        Float'Image(Float(Stop - Start) / Float(The_Data'Length)) &

        " seconds.");

end Parallel_Addition_Test;


The variable The_Data is an instance of Data_Access which accesses an array containing Integer’Last data elements. The variables Start and Stop are used to capture the time required to calculate the sum of all values in the array.
All the values of the array accessed by the variable The_Data are initialized to 1 to ensure that the resulting sum does not exhibit integer overflow. The variables Start and Stop record the time just before summing the data and just after summing the data. The difference in the two time values is the approximate elapsed time to calculate the sum. The average time per addition operation is simply the elapsed time divided by the number of data elements processed.
An output of this program, run on a Windows 10 computer, is:

The sum is:  2147483647

Addition elapsed time is  5.661118000 seconds.

Time per addition operation is  2.63616E-09 seconds.

The sum is also the number of array elements processed. This large array was used to produce a statistically significant timing sample.

  • 16 September 2019 at 05:55
❌
❌