Department of Computer Science and Technology

Raspberry Pi

RPii - C Tutorial Page 2

C.2.6 Structures and flags

This section introduces two concepts which are very important in the RPii Motion Logger, the structure data type, and the idea of flags.

Structures

Structures are nothing to be intimidated by, they are purely a neater way of organising many variables within a program. The libRPii library uses over 50 variables, and applications like the RPii Motion Logger need to continuously change many of these over 10 times a second. Recall that in section C.1.2, it was discussed that functions in programs can take multiple arguments - each of which must be passed within the parenthesis when the function is called. If we want to pass many variables to a function, then passing each one independently would get messy, and function calls would end up being on multiply lines in source files like:

int sum = addallof(input1, input2, input3, … , input 20, …, input 40, …, lastinput);

Structures are used to store many variables under one structure name - that way we can pass lots of variables into and out of a function, with only one argument:

int sum = addallof(inputstructure);

'inputstructure' would contain all of the inputs needed. Structures can hold almost any number of any variables we want it to within memory limits.

In complex programs, structures can contain many structures of structures. In these cases, navigating all of the variables becomes a little like navigating the directories and files of the file system on the Raspberry Pi - where the variables are like the files that we can write in and save, and the directories are just neat ways of storing the files so that they are easier to find. The example below shows how we can declare and initialise structures - they can then be used as we would use any other variable:

ExS.5 Example Source Code: usingstructures.c

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
//define structures
struct axes {
int X;
int y;
int Z;
};

struct variables {
int integer;
float decimal;
char character;
struct axes nest;
};

int main(){

//declare a structure of type 'variables' called var
struct variables var;

//initialise structur var
var.integer=2;
var.decimal = 2.5;
var.character = 'x';

//initialise the axes structure nest which is in var
var.nest.X = 0;
var.nest.Y = -1;
var.nest.Z = 1;

return 0;
}

Important lines to understand in ExS.5 usingstructures.c

When nesting structures, ensure that all structures which are to be declared and nested within another structure have already been defined higher up the source file.

The -> syntax (which you will come across if you use the Exploring libRPii section) has the same use as the full stop when the structure has been pointed to rather than passed by value. Section C.2.9 exaplins what a pointer is.

Line No. Comment
<15. Structures are defined before the main loop. This is necessary to allow the structures to be 'seen' by all functions in the project.
2.- 6. structures hacve data type struct. When defining a structure we follow the format:
  1. struct
  2. structure type followed by an opening curly bracket, {
  3. the declarations of the name and types of each member of the structure,
  4. the closing curly brack and semi-colon to state the end of the definition, };
12. Structures can be nested within other structures. In this line the data type of the structure is struct axes, and the name of this structure is nest.
18. Once a structure has been defined, they are essentially a new data type that we can used. Thus we must follow the same protocol that we used when using any variables: declare a variable of a given type, and then intialise it. This line declares a variable called var of type struct variables
21. We dereference a member of a structure using a full stop '.' . To assign or use the value of a variable in a structure, the location of the variable must be given. e.g we must use var.integer not just integer.
26. When nesting structures, we can use as many full stops as necessary to dereference a variable which is a member in a structure which is a member of another structure, which is a member of another structure and so on.

Flags

The bool data type has not been discussed yet as it is not support in C by default. It is however a very useful data type, as it can only take one of two values, true or false. To use the data type bool, include <stdbool.h>.

Flags are used in the same way as any other variable; they can be of any of the data types which are often used, but are most commonly ints or bools. Moreover they are usually only ever set to a few different values, of which 1 and 0/ true and flase are the most common. They are used for signifying the state of a function or program. We will look at how libRPii chooses to use its button flags and state flags as an example.

Firstly, due to the larger number of flags needed to be stored and checked, it is sensible to store flags in structures within a program [5]. This lets us pass many flags into functions to be checked and reset with only one argument. A large use of flags in libRPii are in the interface functions. If you have used the RPii Motion Logger application, you may have noticed that if you enter the 'Change Settings' or 'Calibrate' modes, and try to start logging with the A button, then the program prints an error and disables you from being able to log until you have saved the new settings. To prevent the user from accidentally running the logging while the settings are half changed, the libRPii sets a flag when the user enters the 'Change Settings' mode. A simple if( flagIsNotSet) statement then controls whether or not the A button will start logging, or will print the error message.

Now consider the button flags. To read which buttons are been pressed on the wiimote, the cwiid library sends messages to the wiimote at roughly 100Hz. Each response is stored in a structure hidden well within the cwiid library, and every new message overwrites the previous button presses. Thus, if we merely call a function will tells us which buttons are currently pressed (in order to check if A is pressed), then we only get a positive response if the A button was pressed at the very same instant that the function ran. This would make it very easy for the A button to be pressed and for the program to not respond. To overcome this difficulty we use a flag. One function continuously checks the messages being received from the wiimote and sets a flag specific to each button to 1 if they are pressed. When the button is released, although the new messages have overwritten the history of the pressed button, the flag stays high and still indicates that the button has been pressed. Within another part of the program, whenever a second function needs to know which buttons have been pressed, it checks all of the flags which have been set, responds to each flag's value and resets them to 0 to indicate that their response has been reacted to. This way, even if both functions are not able to run at the same time, the use of a flag have enabled no button presses to be lost.

C.2.7 Writing our own functions

As you may have found already, as our programs get more and more complicated, the main() function in our source files gets longer and longer. This may not affect the functionality of the program, but when we might need to read through our code, when debugging for example, a long main function can make code hard to follow. As we have already discussed in previous sections, we can use functions which are defined elsewhere in our own code so long as we include the relevant headers and directories. We can also write our own functions and save them in the same source file, this section takes us through how we can go about writing our own functions.

Function outputs

Note that if the function needs no arguments, then we leave the parenthesis empty in the function call and definition.

Just like variables, functions must also have a data type. The data type used specifies the type of the output of a function; e.g if we wanted to write a function which gave an integer as an output, the function would need to be of data type int. Any data type that we use for a variable (including structs and bools) can be used for a function. In addition, there exists the data type void. This is used for functions which do not need to return any value - like a function which turns on a light, or prints a menu to the console. Any none void function must at some point return a value of the same type as the function. This is acheived with a return line of code and is why the main loop must have a return 0; line at the end. For void functions, even though we may not need any output, it is good practice to return some value to idicate what a function did when it was called - e.g return -1 if the function could not be executed due to an error. In complex programs it is crucial to keep track of functions which may fail for one reason or another. Because of this, functions which could be of type void, are often of type int instead and return a value which might be useful for debugging.

Function definitions

A function declaration is known as a function prototype. It is a single line of code which is identical to the definition except that it does not contain the body of the definition. For the example to the left, the prototype would be:
int sqrinput(int a, int b);

Similar to how we use variables, functions used in a source file need to be declared and defined (the equivalent of initialising a variable).Moreover, as with variables we can either declare functions before we define them or we can declare and define them at the same time. A function definition must exist outside of the main functions curly brackets, and contain four main peices of information - consider the example function definition below:

int addintegers (int a, int b){

//declare and initialise variables local to this function
int sum;
sum = a + b;
return sum;
}

Each piece of information needed is explained below:

  • Function data type: This sets the data type of the variable returned (function output). As above, the type of the variable returned (in the above case, sum) must match the type of the function.
  • Function name: This is the name of the function. It is case sensitive, and is used to call the function.
  • Function arguments: Each argument's data type and arbitrary name is declared in between the parenthesis which follow the function name. Each argument is seperated using a comma. By refering to the same arbitrary variable name as used in the argument declaration, our functions can manipulate and use the values which we input to the function. Unless we pass a variable by reference (section C.2.9), even though we may change the definition of an input within the function, this will not change the value of the inputted variable outside of the function.
  • Function body: The code between the curly brackets {} is run when the function is called. This is where we write the code that implements what we would like our function to do.

Note that when we declare and define a function, we specify the data type of each input, however when we call a function, we input the variable name without the data type.

Calling a function

Just like calling the printf function in the previous examples and exercises, we can use a function in the main or any other function by writing the name of the function with appropriate arguments. If we wish to save the output value of a function to a memory location/variable, then we can simple assign the variabe to the function call. e.g to call the function written above, we would use a line:

int sumof = addintegers(3,4);
This line would assign the variable integer 'sumof' with the value 7.

ExS.6a Example Source Code: firstfunction.c

Note that in this source file, the function is declared and defined above the main loop. For neatness we often declare all of the functions in the source file at the top of the file, but do not define them until further down. If a source file uses a function before it is declared or defined, the compiler will return an error.

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
//Example Source ExS.6a, firstfunction.c
#include <stdio.h>

//function definition
int sqrinput(int input){
int sqr = input*input;
return sqr;
}

int main(){
int input = 5;
int output = sqrinput(input);
printf("%d squared is %d.\n", input, output);
return 0;
}

//This code is not used. It is an example of how sqrinput could also be defined.
//Any code placed betwee /* and */ is block commented out,
//so the compiler will ignore this code if we were to compile this.
/*int sqrinput(int input){
return input*input;
}*/

Function Prototypes

If we were to explore one of the many header files stored on our Raspberry Pi, we would find that a large section of the file is devoted to function prototypes. When we #include a file, the compiler actually copies the contents of the included file into the file to be compiled. This ensures that(should the programmer have included all of the necessary header files), there is a prototype for each function used in the program.

Function prototypes have been briefly introduced earlier in this section. To understand why we use them, it helps to understand a little more about the compiler.

When the compiler is called one of the things it does before it attempts to compile is check your code. One of the ways it does this is by checking that the arguments supplied to a function that we have called match the expected quantity, and type of arguments used when the function is defined. If these do not match then an error is returned by the compiler. (Spelling mistakes and forgeting to change the function call when the defintion is changed can lead to such errors).

Now consider the order in which the compiler reads our source files; top to bottom, line by line makes sense (which it does). However, if in a function (say main()) another function is called before it has been defined, then the compiler has no way of checking at that point in the file whether or not the function has been called correctly - not great! Ok, so why dont we just ensure that all of our code is defined in the order that its used? Well that might not even be possible - instances exist whereby there are two programs which both call the other, so whichever was defined first would have the second function undefined!

This brings us to prototypes. We can use prototypes at the top of every source file to prevent the compiler from having the problems described. These allow us to describe everything about the function without yet defining it; hence why the prototype is essentially the declaration of the function. Furthermore by using prototypes, finding out which functions exist in which source files in easier - as they will all appear at the top of the file.

ExS.6b Example Source Code: firstfunction.c with function prototype

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
//Example Source ExS.6b, firstfunction.c with function prototype
#include <stdio.h>

//function prototype
int sqrinput(int input);

int main(){
int input = 5;
int output = sqrinput(input);
printf("%d squared is %d.\n", input, output);
return 0;
}

//function definition
int sqrinput(int input){
int sqr = input*input;
return sqr;
}

Exercise 7: A Basic Calculator Continued

We are going to build upon the source file created in exercise 5 to write a program which can subtract or add two integers depending on the inputs we give it. We will write 3 of our own functions, and use the main() loop to control when to execute each function. We will use a while loop so that we can exit the program whenever we want, and use a flag as the argument to the while loop.

It is common practice for the main loop to return 0 when it has run successfully, and any none zero value if it exited with an error.

It is also good practice to check that our program compiles at every few more lines added. This can make errors easier to find as we know they must be cause by the newest code written.

  1. Open the CalculatorSources directory and create a new file. Call this whatever you like.
  2. First we need to decide on the controls for the program. Let's use the following:
    • 0: To exit the program
    • 1: To input two numbers
    • 2: To select 'ADD'
    • 3: To select 'SUBTRACT'
  3. Create a template for the program which contains the following:
    • A section which includes all of the necessary header files - stdio.h is all we need.
    • A main loop which returns 0 at the end.
    • An area above the main loop to contain the function prototypes - we can mark this now with a comment.
    • An area below the main loop for the function definitions.
    It will also be useful to create a makfile to compile this code.
  4. Write a short function, void printcontrols() to print out the controls above (this function needs no inputs). This only needs to contain a few printf lines. Define this function underneath the main function and use the ExS.6 examples to write a prototype for the function. Call this function from the main loop and compile and run this program before you continue.
  5. Add code to the main loop which uses scanf to read in an integer from the keyboard, and use an if(), 3 else if() and an else() statement to printout whether the number inputed is 0, 1, 2, 3 or invalid. If the input is 'invalid', exit the program by causing a return command to be run. (for this part do not use format specifiers, these if() statements will be used to control what is executed when each input is given)
  6. Now add the following to your main loop:
    • Initialise an integer variable named 'flag' as 0 at the start of the program.
    • Around the scanf() and if() statements, add a while loop which is conditional on flag = 0. You may wish to move the printcontrols function call outside of the main loop so that the controls are not printed everytime the code loops!
    • Within the statement which prints out if the input is 0, add a line of code to change the value of the variable flag to 1. When 0 is selected as an option, this flag will now be set to 1. As flag will no longer equal 0 the while loop will stop and the program will end.
    Try to recompile the program - eliminate any errors and only move on once the program is working to this point. The program should continually ask for new values until 0 or an invalid character is given.
  7. Now let's write our functions to add and subtract:
    • let both functions be of type int, with two integer inputs each. So long as we are consistent when referencing these inputs, we can call them whatever we like. Let's call them a and b.
    • Use ExS.6b as an example and write the prototypes and function definition for both the add and subtract functions.
    • Now call each function is such a way that the result of the correct calculation is printed when the user inputs 2 or 3 as an option. Use a printf statement to print out the result of the function, given the inputs a and b. This can be done by assigning the output of the function to a variable, and by printing that variable, or by using the function as an argument in a printf function.e.g
    printf("2 add 3 is %d\n", add(2,3));
  8. Next we need to write a few more lines in order to read in 2 inputs from the command line to use in the calculations. We should first declare these:
    When a function is called, the program assigns all of the memory which is needed for the variables in the function to what is known as a stack. It copies the values of the inputs from the function that calls it, onto the stack of the new function. This stack is 'seperate' from that used by the main function - which is why the names of the input in the function definition are arbitrary. When the function returns, its stack is destroyed.
    • Think about where best in the code to declare them, and of a suitable value to initialise them to such that we can check for errors once we use scanf.
    • Think about what to call them - they do not need to be a and b, but if we were to call them this, they would not intefere with the a and b used in our functions.
    Now consider how we can use scanf to read in two inputs - we can either import them in the same call, or one at a time e.g:
    printf("Input two numbers to use in the calculation.\n");
    scanf("%d %d", &a, &b );
    Or
    printf("Input two numbers to use in the calculation.\n");
    scanf("%d", &a );
    scanf("%d", &b );
    Using the examples above, add a way for your program to save the values of two inputs from the command line. Ensure that the functions add() and subtract() cannot be called without their inputs being initialised first - i.e make sure that the program has asked for the values of the inputs from the user before it lets the user call the add and subtract functions. Also add this code to option 1, so that the user can change their inputs without restarting the program.

This exercise has demostrated how we can write extra functions, and use them in the main function. The same processes are used to call functions within functions. Clearly we would not usually need to create extra functions purely to add or subtract two numbers - we managed it in Exercises 3 and 4. Using all of the theory so far in this tutorial (for loops are particularly helpful), try implementing the following functions if you have come across them:(but if you start adding a lot, see section C.2.8 first)

  • reciprocal
  • factorial
  • exponential
  • Conversion of units, e.g metric to imperial (some research will be required)
  • Quadratic Solver using the quadratic equation

Implementing the following functions willrequire using some of the above functions:

  • The combinations, nCr function
  • The permutations, nPr function

C.2.8 The switch() statement

During exercise 7, all of those elseif statements started to look a bit cluttered. Once we start to have more than 4 different cases which we need to act on in a program, we tend to use a switch statement instead. It has quite particular syntax but is very clear to understand, and is great for handling things like menus.

A switch statment takes one argument, a variable which is able to be any of a set of values. For each value it can take, we can then define what the program will do. There is also a default case which can be used like the else() statement, to handle any value which hasnt been handled specifically. Example Source ExS.7 shows how we could have used a switch() statement to handle the menu of options used in exercise 7.

ExS.7 Example Source Code: usingswitch.c

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
#include <stdio.h>

int main(){
int input = -1;
printf("Give a number between 1 and 5\n");
scanf("%d", &input);
switch (input) {


case 1:
//when the input is 1, this section of code is executed
printf("input is 1\n");
break;

case 2:
//when the input is 2, this section of code is executed
printf("input is 2\n");
break;

case 3:
//when the input is 3, this section of code is executed
printf("input is 3\n");
break;

case 4:
//when the input is 4, this section of code is executed
printf("input is 4\n");
break;

case 5:
//when the input is 5, this section of code is executed
printf("input is 5\n");
break;

default:
//if the input equals anything which does not have a case description
//then this code is executed
printf("error\n");
return 1;
return 0;
}

If you would like to get more comfortable using a switch statement, try to do the last parts of exercise 7 with a switch statement instead of the if and else if statements.

C.2.9 Passing by reference and Pointers

In C there are two ways for us to pass variables between functions - we can either copy the value of a variable into the new function, or we can direct/point the new function to the location of the variable. These are called passing by value, and passing by reference repectively. This section briefly introduces how we can use both of these techniques, and what to be careful about when we do so.

Just to clarify the fundamental differences, consider this analogy:

We have an original text document called 'original.txt' in an arbitrary directory. Our aim is to be able to access the content of this document from our desktop - so how can we do this? We can either copy the document to the desktop, making a new file seperate to the old one but with the same content, or we could copy a shortcut to the desktop. When we click on the shortcut our computer will open the document which is saved in the original directory, however if we clicked on the copied document our computer would open the document which is saved in the desktop directory. Now what happens when we edit both of the documents opened from the desktop; if we changed the content of the document copied to the desktop, then the original file would still be the same as the file on the desktop is saved in a different place from the original. On the other hand, if we changed the content of the file opened from the shortcut, the original file would be changed.

In this analogy copying is equivalent to passing by value - the program copys the content/value of the variable from where the function is called, into the function. This means that if we change the value of a variable passed by value into a function, the value is only changed in the function, and not in the place from which the variable was passed. Creating a shortcut is equivalent to passing by reference. If we pass by reference then any changes to the variable in the function will also affect the value of the original input in the area of the program which called the function.

We refer to what is passed instead of a value (when we pass by reference) a pointer, as it points to a memory address. Pointers are a data type in themselves and are denoted by prefixing the variable with a *. Thus, if we want to write a function which takes pointers as inputs, then we must declare the arguments in the function definition and prototypes as pointers.

If we want to pass a variable in such a way that another function can change its contents then we must pass the variable by reference. This is denoted by using the ampersand '&' symbol in the function argument. Irrelevant of the size of the variable being passed, passing by reference only gives a memory location to the new function which is often 32bits long (the same length as an int type). In comparison if we were passing by value then the infomation passed is as large as the variable is - which is often many times larger than 32 bits. Consequently, large data structures are often passed by reference instead to speed up programs.

Given the performance benefits, passing by reference seems to be the obvious choice - which it is a lot of the time, however using them can lead to many more compiler errors. The examples below show how we would write a function with pointers as inputs, and how we would pass the variables.

ExS.8 Example Source Code: usingpointers.c

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
#include <stdio.h>

//function prototypes
int passByValCount(int value);
int passByValCount(int *a);

int main(){

//declare and initialise variable
int a = 5;
//Call functions and check value of a
printf("At the start of main, a = %d.\n",a);
passByValCount(a);
printf("In main, after passByValCount returns, a = %d.\n", a);
passByRefCount(&a);
printf("In main, after passByRefCount returns, a = %d.\n", a);
return 0;
}

//function definitions
//These functions print a countdown from their input
int passByValCount(int value){
for(; value >0; value--){
printf("%d\n",value);
}
printf("In passByValCount, a = %d when the function returns.\n", value);
return 0;
}

int passByRefCount(int *pointer){
for(; (*pointer) >0; (*pointer)--){
printf("%d\n",(*pointer));
}
printf("In passByRefCount, a = %d when the function returns.\n", pointer);
return 0;
}

Running this program gives the following:

pi@raspberrypi ~ $ ./usingpointers
At the start of main, a = 5.
5
4
3
2
1
In passByValCount, a = 0 when the function returns.
In main, after passByValCount returns, a = 5.
5
4
3
2
1
In passByValCount, a = 0 when the function returns.
In main, after passByRefCount returns, a = 0.

Important lines to understand in ExS.8 usingpointers.c

Retrieving the value of a variable stored in a pointer is known as dereferencing.

Line No. Comment
5. As we expect a pointer input into the passByRefCount function, we declare the input with a * in front of the variable a. Even though a pointer is not an int, the value pointed at is, and so we must tell the compiler what data type the pointer points to.
15. To pass the variable 'a' by reference, we prefix the variable a with an ampersand, &. The ampersand passes a pointer which points to the memory location of the variable a in the main function.
23. and 31. As we do not plan to intialise the value of a in the for loop, we can leave the first argument empty.
31. Note that we did not pass a value into this function - we passed a memory location. Thus, if we tried to print the value of 'pointer' (try this for yourself), we would get a long series of numbers which make no sense in the context that we want to evaluate 'a' in. We tell the program to use the value in the location of a pointer by putting (* ) around the pointer; this is called dereferencing.
31. Now because 'pointer' points to the location of the variable 'a' in the main function, (*pointer) accesses the variable stored in 'a's' location in the main function. Thus, as we change the value of (*pointer) as we decrement it, we overwrite the value in the memory location of 'a', and thus when the function returns the variable 'a' now equals 0.

Pointers are used extensively in libRPii, however due to the complexity of using them, the API of the library is designed to hide their use as much as possble. Due to this, this tutorial does not cover their use any further than this brief explanation - however using them will be discussed more when it is relevant in the Exploring libRPii section.

C.2.10 Error Handling

Coming soon...



Link to homepage