❌ About FreshRSS

Normal view

There are new articles available, click to refresh the page.
Before yesterdayNews from the Ada programming language world

C++ versus Ada for safety critical software (ii)

By: spqr
2 April 2021 at 16:35

The problem with switch

The switch statement in C/C++ is horrible. It was never really that well thought out. The main problem is that the default label is entirely optional. There is no requirement to handle all possible values of an expression. The other issue is the break statement, which if not included may result in an unintended fall-through. In comparison, Ada provides a case statement which is safer. Firstly, a case statement in Ada must cover every possible value of the case expression, it will be checked at compile time. For example here is an example which classifies temperatures for an oven.

with ada.Text_IO; use Ada.Text_IO;
with ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure safecase is

   n : integer;

begin
   put("Enter an oven temperature (F): ");
   get(n);
   case n is
      when 200 .. 299 => put_line("cool oven");
      when 300 .. 325 => put_line("slow oven");
      when 326 .. 350 => put_line("moderately slow oven");
      when 351 .. 375 => put_line("moderate oven");
      when 376 .. 400 => put_line("moderately hot oven");
      when 401 .. 450 => put_line("hot oven");
      when 451 .. 500 => put_line("very hot oven");
      when others => put_line("temperature out of bounds");
   end case;

end safecase;

If the others line were omitted, then the following compiler error would propagate, implying values 199 and below, and 501 and above are not accounted for.

safecase.adb:11:04: missing case values: -16#8000_0000# .. 199
safecase.adb:11:04: missing case values: 501 .. 16#7FFF_FFFF#

A when branch can specify a single value, or a range of values, or any combination separated by the | symbol. The optional others branch covers the values not included in earlier branches. When execution of the statements in the selected branch has been completed, control resumes after the end case statement. Unlike C++, Ada does not allow fall-through to the next branch.

The problem with statements

In C/C++ there are simple statements , and compound statements enclosed in {}. It is therefore easy to create a logic error simply by failing to enclose more than one statement in a compound. Below is an example of a piece of code in which the logic is erroneous:

for (i=0, i<50; i=i+2)
   a[i] = 0;
   a[i+1] = 1;

The code is not contained within a compound statement, so only the first statement is associated with the for loop. Ada has no concept of a simple statement, and all control structures are β€œblocked”. For example the code above in Ada would be:

for i in 1..50 loop
   a(i*2-1) := 0;
   a(i*2) := 1;
end loop;

The problem with function returns

C++ only provides a single type of subprogram – the function. It is possible for a function to omit the name of the return type, in which case the return type is set to int. A function that returns nothing (i.e. a procedure) has a void return type. Theoretically every function should have a return type as it should be used to indicate whether a function succeeds or fails (hence the default to int). Many programmers of course don’t bother with indicating the success of a function, and hence ignore the return value. Failure to heed a function failure can result in the failure of a safety-critical system. Consider the following C++ program:

#include <iostream>

int doubled(int x){
   return x*2;
}

int quadruple(int x){
   return doubled(doubled(x));
}

int main(){
   quadruple(5);
   return 0;
}

When this is compiled (even with warnings) there is no issue, even though the function call to quadruple() is treated like like a statement, with the return sent off to the nether world. It is then easy to have such failings happen in a large program, and hard to trace a source of error. Ada return values can be used for the same purposes as C/C++ return values, but they cannot be ignored. Here is the same program coded in Ada.

with ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure safefunc is
   n : Integer;

   function quadruple (x: Integer) return Integer is
      function double (x: Integer) return Integer is
      begin
         return x * 2;
      end double;
      result : Integer;
   begin
      result := double(double(x));
      return result;
   end Quadruple;

begin
   quadruple(5);
end safefunc;

Compiling this will lead to an error on line 18, the source of the call to quadruple():

safefunc.adb:18:04: cannot use call to function "quadruple" as a statement
safefunc.adb:18:04: return value of a function call cannot be ignored

This has to be fixed by adding a return value, of the form:

n := quadruple(5);

Ada was designed to support safety in modern safety critical systems. The examples outlined offer an insight into how Ada is better than C++ for such systems. Neither C nor C++ were designed to support the needs of real-time safety-critical systems.

spqr

C++ versus Ada for safety critical software (i)

By: spqr
1 April 2021 at 17:33

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;

begin
   for x in image1'range(1) loop
      for y in image1'range(2) loop
         image1(x,y) := 0;
      end loop;
   end loop;

end safearrays;

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.

#include <iostream>
#include <cstring>

using std::cout;
using std::endl;

int main(){
   char str[10];
   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;
   return 0;
}

The output is as expected from C++ – the first 10 uppercase characters followed by a bunch of garbage, and a length of 11.

    ABCDEFGHIJ
    
    `οΏ½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);
begin
   str := "ABCDEFGHIJ";
   put(str);
   put(str(11));
end safestr;

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.

spqr

C++ versus Ada for safety critical software (ii)

By: spqr
2 April 2021 at 16:35

The problem with switch

The switch statement in C/C++ is horrible. It was never really that well thought out. The main problem is that the default label is entirely optional. There is no requirement to handle all possible values of an expression. The other issue is the break statement, which if not included may result in an unintended fall-through. In comparison, Ada provides a case statement which is safer. Firstly, a case statement in Ada must cover every possible value of the case expression, it will be checked at compile time. For example here is an example which classifies temperatures for an oven.

with ada.Text_IO; use Ada.Text_IO;
with ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure safecase is

   n : integer;

begin
   put("Enter an oven temperature (F): ");
   get(n);
   case n is
      when 200 .. 299 => put_line("cool oven");
      when 300 .. 325 => put_line("slow oven");
      when 326 .. 350 => put_line("moderately slow oven");
      when 351 .. 375 => put_line("moderate oven");
      when 376 .. 400 => put_line("moderately hot oven");
      when 401 .. 450 => put_line("hot oven");
      when 451 .. 500 => put_line("very hot oven");
      when others => put_line("temperature out of bounds");
   end case;

end safecase;

If the others line were omitted, then the following compiler error would propagate, implying values 199 and below, and 501 and above are not accounted for.

safecase.adb:11:04: missing case values: -16#8000_0000# .. 199
safecase.adb:11:04: missing case values: 501 .. 16#7FFF_FFFF#

A when branch can specify a single value, or a range of values, or any combination separated by the | symbol. The optional others branch covers the values not included in earlier branches. When execution of the statements in the selected branch has been completed, control resumes after the end case statement. Unlike C++, Ada does not allow fall-through to the next branch.

The problem with statements

In C/C++ there are simple statements , and compound statements enclosed in {}. It is therefore easy to create a logic error simply by failing to enclose more than one statement in a compound. Below is an example of a piece of code in which the logic is erroneous:

for (i=0, i<50; i=i+2)
   a[i] = 0;
   a[i+1] = 1;

The code is not contained within a compound statement, so only the first statement is associated with the for loop. Ada has no concept of a simple statement, and all control structures are β€œblocked”. For example the code above in Ada would be:

for i in 1..50 loop
   a(i*2-1) := 0;
   a(i*2) := 1;
end loop;

The problem with function returns

C++ only provides a single type of subprogram – the function. It is possible for a function to omit the name of the return type, in which case the return type is set to int. A function that returns nothing (i.e. a procedure) has a void return type. Theoretically every function should have a return type as it should be used to indicate whether a function succeeds or fails (hence the default to int). Many programmers of course don’t bother with indicating the success of a function, and hence ignore the return value. Failure to heed a function failure can result in the failure of a safety-critical system. Consider the following C++ program:

#include <iostream>

int doubled(int x){
   return x*2;
}

int quadruple(int x){
   return doubled(doubled(x));
}

int main(){
   quadruple(5);
   return 0;
}

When this is compiled (even with warnings) there is no issue, even though the function call to quadruple() is treated like like a statement, with the return sent off to the nether world. It is then easy to have such failings happen in a large program, and hard to trace a source of error. Ada return values can be used for the same purposes as C/C++ return values, but they cannot be ignored. Here is the same program coded in Ada.

with ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure safefunc is
   n : Integer;

   function quadruple (x: Integer) return Integer is
      function double (x: Integer) return Integer is
      begin
         return x * 2;
      end double;
      result : Integer;
   begin
      result := double(double(x));
      return result;
   end Quadruple;

begin
   quadruple(5);
end safefunc;

Compiling this will lead to an error on line 18, the source of the call to quadruple():

safefunc.adb:18:04: cannot use call to function "quadruple" as a statement
safefunc.adb:18:04: return value of a function call cannot be ignored

This has to be fixed by adding a return value, of the form:

n := quadruple(5);

Ada was designed to support safety in modern safety critical systems. The examples outlined offer an insight into how Ada is better than C++ for such systems. Neither C nor C++ were designed to support the needs of real-time safety-critical systems.

spqr

C++ versus Ada for safety critical software (i)

By: spqr
1 April 2021 at 17:33

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;

begin
   for x in image1'range(1) loop
      for y in image1'range(2) loop
         image1(x,y) := 0;
      end loop;
   end loop;

end safearrays;

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.

#include <iostream>
#include <cstring>

using std::cout;
using std::endl;

int main(){
   char str[10];
   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;
   return 0;
}

The output is as expected from C++ – the first 10 uppercase characters followed by a bunch of garbage, and a length of 11.

    ABCDEFGHIJ
    
    `οΏ½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);
begin
   str := "ABCDEFGHIJ";
   put(str);
   put(str(11));
end safestr;

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.

spqr

❌
❌