Safety critical software is incredibly important for systems designed for applications such as aerospace, rail transportation, power stations and the like. This the F-35 hey, built largely in C++. In some cases these systems have been built in languages such as C and C++, largely because they have a very inexpensive labour force. However the use of these languages, as opposed to Ada have significant hidden costs and incur safety risks due in part to the unweildy and awkward nature of the languages. This post compares some of the features of C++ and Ada.
The problem with arrays
There are a number of issues with arrays in C++:
- Every array index must begin at zero. Due to this array indexing is always off-by-one from the element number of the array resulting in a tendency to index past the end of the array.
- C++ does not do bounds checking automatically, instead relying on it to be manually coded.
- The use of 0-based arrays means that 2D arrays are indexed using a single index which is derived using a formula. This maps very closely to how the array is stored in memory, but does not support the idea of matrix in an unambiguous manner.
- Arrays are indexed using integers.
In comparison, Ada arrays include a declaration of both the index-type, and the element type. All operations performed on the array are subjected to bounds checking, and the array indices may be of any discrete type. Below is an example of creating an array in Ada to hold a 1000×1000 image.
procedure safearrays is
subtype imgIndex is integer range 1..1000;
subtype uint8 is integer range 0..255;
type image is array(imgIndex,imgIndex) of uint8;
image1 : image;
for x in image1'range(1) loop
for y in image1'range(2) loop
image1(x,y) := 0;
Subtypes can be used to constrain the valid range on index and array values. In this case the subtype uint8 is created to hold unsigned integers with values 0..255, and the subtype imgIndex is used to hold the index range for the image. The type image is then created using these types. If any attempt is made to access an element outside the array image, the following error occurs (where X is the line number):
raised CONSTRAINT_ERROR : safearrays.adb:X index check failed
The Ada syntax has a number of benefits. Firstly Ada arrays are first class types, not structures created as an afterthought using pointers. No pointers are involved, to at least they are handled internally. Ada also implicitly checks array bounds, meaning unlike C/C++ there will never be an issue with accessing a piece of memory not associated with an array. The Ada Range attribute means that bounds do no have to be hard coded, which makes it easier to maintain a piece of code.
The problem with strings
Let’s face it strings in C/C++ are generally horrible. Nobody should have to deal with a string terminator, the null character, ‘\0’. This means strings always have to be one element larger than the characters being stored. This means if a string is created, the null character must be added, otherwise there is the risk of issues with array bounds, or garbage being extracted from elements with no value. C/C++ provide a whole string library to manipulate strings, for example strcpy() to copy a string. Like numeric arrays, no bounds checking is performed on strings.
There are of course inherent safety concerns with C++ strings. The first is that they do not automatically maintain information about the length of the information contained in a string, and need the strlen() function to do that, assuming the string is null terminated. The second is that they do not prevent information being stored beyond the physical end of the string. A missing null character might mean the standard string functions process beyond the bounds of the string. C++ does provide a String class. Below is an example which creates a string, str, of length 10 (although in reality it should only hold 9 characters). Values are then assigned to all 10 elements of str, and 20 elements of str are printed out, as well as the length of the string.
for (int i=0; i<10; i=i+1)
str[i] = 65+i;
for (int i=0; i<20; i=i+1)
cout << str[i];
cout << strlen(str) << endl;
The output is as expected from C++ – the first 10 uppercase characters followed by a bunch of garbage, and a length of 11.
Ada provides a predefined array fixed type, String. This can either be specified using bounds (line 1), or an unconstrained array (line 2), where the bounds are deduced from the initialization expression.
1 quoteyoda : String(1..30) := "Do or do not, there is no try!";
2 quoteyoda : constant String := "Do or do not, there is no try!";
Ada provides three forms of strings: the type String is a fixed length string, the type Bounded_String provides a fixed buffer than contains string data up to the length of the buffer, and Unbounded_String which uses a dynamically allocated buffer to contain data without having to define the length. There is no need for special functions to copy from one String to another, or one Bounded_String to another, however functions are provided to convert between types, for example a String to an Unbounded_String. The main benefit of strings in Ada is that memory outside the buffer associated with the string cannot be corrupted. Here is a simple example in Ada which tries to access the 11th element of the array str.
with Ada.Text_IO; use Ada.Text_IO;
procedure safestr is
str : String(1..10);
str := "ABCDEFGHIJ";
It is not possible. Compiling it produces the following messages:
safestr.adb:8:12: warning: value not in range of subtype of "Standard.Integer" defined at line 4
safestr.adb:8:12: warning: "Constraint_Error" will be raised at run time
And executing the code causes “raised CONSTRAINT_ERROR : safestr.adb:8 range check failed“. So Ada catches the error, and C++ using the standard array of characters fails to.