Some C-tricks and Tips for Real-time Programming Mikael Sundstrom 1. Arrays and Pointers In C, an array is represented by a pointer to its first element. Does this mean that all pointers are arrays? The answer is both yes and no! Any pointer p can be used as an array in the sense that element i is represented by p[i]. However, p does not necessary "point to" (or refer to) a memory location where we are allowed to read or write. Or, the index i may be so large that p[i] is located beyond the end of the "array p points to". There is no automatic range checking in C! Consider the following two declarations: int a[10]; int *p; The second declaration actually means int* p; That is, the asterisk is part of the declaration but it is convention in C to write it close to the variable name. Now, both a and p are pointers to integers as well as arrays of integers. By the declaration of a, it refers to a memory area where we can store 10 integers. However, this memory area is generally not initialized (or it might be initialized with zeroes). One could say that a is initialized as a pointer and we can write a[0] = 5; a[4] = 2; and so on. What happens if we write p[0] = 5; p[4] = 2; instead? Well, the expected result is a crash at the first line and the reason is that p is not initialized as a pointer and probably refers to some random memory location where we are not allowed to write (or read for that matter). By declaring p as we did, there is no memory area created which p points to as with a. It is just a pointer to somwehere! By assigning p a valid address we can use p exactly as we use a. In particular, by first assigning the address of the first element of a to p as p = &a[0]; or equivalently but simpler p = a; we can use p[0] = 5; p[4] = 2; Why are not all arrays declared using the "array style" as with a? The answer is that a is constant as a pointer. For example, if we have function int *f(void); that returns a pointer to an integer, we can call f and store tha result in p p = f(); but we can not store the result in a. That is a = f(); will not pass the compiler. As a last example, lets assume that we want to use the the 5 element array constituting the second half of the array a. Then we can assign p = &a[5]; or equivalently but more C stylish p = a + 5; Then we can use p[0], ..., p[4]. It is possible to declare a pointer as int r[] but only when it is the argument to a function. The effect is exactly the same as if r was declared as p. 2. Command Line Arguments The traditional prototype of main is as follows. int main(int argc, char *argv[]); Main takes two arguments traditionally called argc and argv where argc is the number of arguments including the program name itself and argv is an array of the arguments where the first element argv[0] is the program name itself. The arguments are represented as arrays of characters that are '\0' terminated. I guess you could call such character arrays strings. The following example main function shows how to deal with program arguments by printing them. int main(int argc, char *argv[]) { int i; printf("program name: %s\n", argv[0]); for (i = 1; i < argc; i++) { printf("argument %3d: %s\n", i, argv[i]); } } If the strings are to be interpreted as something else (like integers) a conversion needs to take place. 3. Input, Output and Conversion There are a number of special functions available for converting between integers and strings, strings and floats and so on. I would recommend not using any of them. It is much better to learn how to use the printf/scanf family of functions and then use these exclusively. For example, if you want to convert an integer int i; to a string and store the result in a buffer char buf[256]; the simplest way to achieve this (assuming you know how to use printf) is to use sprintf (printf into a string) as follows sprintf(buf, "%d", i); The inverse operation, i.e. converting a string to an integer, is achieved using sscanf (scanf from a string) sscanf(buf, "%d", &i); Regarding input the functions scanf and fscanf used for scanning and formatting input from stdin or any stream respectively are traditionally considered unsafe and are not recommended to use (see manual pages for scanf). It is better to read one line at a time into a buffer using gets or fgets and then use sscanf to parse the buffer and extract the input. Suppose that you have written some output to a file using fprintf(stream, FORMAT, x, y, z); for some stream that is previously opened (like stderr but output are written to a file), some format string FORMAT and some variables x, y, z. Then, reading from the same file can easily be chieved by using fgets to read each line into char buf[256]; // assumed to be large enough. and then use sscanf to parse the line sscanf(buf, FORMAT, &x, &y, &z); using exactly the same format string. This is an easy way of producing data files that are readible by humans but still easy to read by a computer. Assuming that x, y, z are integers, the format string used could be #define FORMAT "The value of x, y, and z are %d, %d and %d respectively\n" and a line of the output file produced by calling fprintf(stream, FORMAT, 5, 8, 1); would look like: The value of x, y, and z are 5, 8 and 1 respectively 4. Using the Pre-processor to Separate Code in Lab Assignments In many of the lab assignments, there are several steps and some of the code produced is used in one step but replaced in the next step. One way of solving this is to have one souorce code file for each step but that results in lots of unnecessary printouts and code etc. To avoid having separate source code files for each step you can use the pre-procesor as in the following exmple #if STEP == 1 #elif STEP == 3 || STEP == 5 #else #endif In the beginning of the source fils (above all of this) you have the line #define STEP 1 when compiling step 1, #define STEP 2 when compiling step 2 and so on. In this way you can write the source code for several steps in the same file and it is still easy to see which code that belongs to a certain step and which code that is used throughout several steps. The pre-processor strips away code after #if statements where the condit ion is false. An alternative way of doing this is to use #ifdef DEBUG #endif or equivalently #if defined(DEBUG) #endif where the condition is satisfied if the macro DEBUG is defined. This method is commonly used in cross platform operating systems for example for separating special code for different architectures #ifdef INTEL #endif #ifdef POWERPC #endif etc.