This article is part of the Embedded Software series: Ada for the Embedded C Developer
As the title notes: What is Ada?
To answer this question, let's introduce Ada as it compares to C for an embedded application. C developers are accustomed to a certain coding semantic and style of programming. Especially in the embedded domain, developers are used to working at a very low level near the hardware to directly manipulate memory and registers.
Normal operations involve mathematical operations on pointers, complex bit shifts, and logical bitwise operations. C is well-designed for such operations as it is a low-level language designed to replace assembly language for faster, more efficient programming. Because of this minimal abstraction, the programmer must model the data that represents the problem they’re trying to solve using the programming language.
Here we have a piece of code in C (Fig. 1) and in Ada (Fig. 2) that takes some numbers from the command line and stores them in an array. We then sum all of the values in the array and print the result. The tricky part is that we’re working with values that model an angle in degrees. We know that angles are modular types, meaning that angles greater than 360° also can be represented as Angle mod 360. So, if we have an angle of 400°, this is equivalent to 40°.
Figure 1: This fragment of C code takes some numbers from the command line and stores them in an array.
// main.c #include#include #define DEGREES_MAX (360) typedef unsigned in degrees; #define MOD_DEGREES(x) (x % DEGREES_MAX) degrees add_angles(degrees* list, int length) { degrees sum = 0; for(int i = 0; i < length; ++i) { sum += list[i]; } return sum; } int main(int argc, char** argv) { degrees list[argc – 1]; for(int i = 0; i < argc; ++i) { list[i – 1] = MOD_DEGREES(atoi(argv[i])); } printf("Sum: %d\n", add_angles(list, argc-1)); return 0; }
Figure 2: This fragment of Ada code also takes some numbers from the command line and stores them in an array.
-- sum_angles.adb with Ada.Command_Line; use Ada.Command_Line; with Ada.Text_IO; use Ada.Text_IO; procedure Sum_Angles is DEGREES_MAX : constant := 360; type Degrees is mod DEGREES_MAX; type Degrees_List is array (Natural range <>) of Degrees; function Add_Angles(List : Degrees_List) return Degrees is Sum : Degrees := 0; begin for I in List’Range loop Sum := Sum + List(I); end loop; return Sum; end Add_Angles; List : Degrees_List (1 .. ArgumentCount); begin for I in List’Range loop List(I) := Degrees(Integer'Value(Argument(I))); end loop: Put_Line("Sum:” & Add_Angles(List)'Img); end Sum_Angles;
Modeling for C
To model this behavior in C, we had to create the MOD_DEGREES macro, which performs the modulus operation. As we read values from the command line, we convert them to integers and perform the modulus before storing them into the array. We then call add_angles, which returns the sum of the values in the array. Can you spot the problem with the C code?
Try running the Ada and C examples using the input sequence 340 2 50 70. What does the C program output? What does the Ada program output? Why are they different?
The problem with the C code is that we forgot to call MOD_DEGREES in the for loop of add_angles. This means that it’s possible for add_angles to return values greater than DEGREES_MAX.
Ada Version
Let's look at the equivalent Ada code now to see how Ada handles the situation. The first thing we do in the Ada code is to create the type Degrees, which is a modular type. This means that the compiler is going to handle performing the modulus operation. If we use the same for loop in the Add_Angles function, we can see that we aren't doing anything special to make sure that our resulting value is within the 360° range we need it to be in.
The takeaway from this example is that Ada tries to abstract some concepts from the developer. Therefore, the developer can focus on solving the problem at hand using a data model that models the real world rather than using data types prescribed by the hardware. The main benefit of this is that the compiler takes some responsibility from the developer for generating correct code.
In this example, we forgot to put in a check in the C code. The compiler inserted the check for us in the Ada code because we told the compiler what we were trying to accomplish by defining strong types.
Power or Accuracy or Both?
Ideally, we want all of the power that the C programming language can give us to manipulate the hardware we’re working on while giving us the ability to more accurately model data in a safe way. Thus, we have a dilemma: What can give us the power of operations like the C language, but also provide us with features that can minimize the potential for developer error? Since this course is about Ada, it's a good bet we're about to introduce the Ada language as the answer to this question…
Unlike C, the Ada language was designed as a higher-level language from its conception, giving more responsibility to the compiler to generate correct code. As mentioned above, with C, developers are constantly shifting, masking, and accessing bits directly on memory pointers. In Ada, all of these operations are possible. However, in most cases, there’s a better way to perform these operations using higher-level constructs that are less prone to mistakes, like off-by-one or unintentional buffer overflows.
If we were to compare the same application written using C and with Ada using high-level constructs, we would see similar performance in terms of speed and memory efficiency. If we compare the object code generated by both compilers, it's possible that they even look identical!
Read more from the Embedded Software series: Ada for the Embedded C Developer