Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Programming Microcontrollers in C, 2-nd edit (Ted Van Sickle, 2001).pdf
Скачиваний:
306
Добавлен:
12.08.2013
Размер:
7.2 Mб
Скачать

Chapter 2

Advanced C Topics

Pointers

The use of pointers sets the C language apart from most other high-level languages. Pointers, under the name of indirect addressing, are commonly used in assembly language. Most high-level languages completely ignore this powerful programming technique. In C, point­ ers are simply variables that are the addresses of other variables. These variables can be assigned, be used as operands, and treated much the same as regular variables. Their use, however, can simplify greatly many of the truly difficult problems that arise in day-to-day program­ ming. Let’s discuss pointers with the view that they offer us a powerful new tool to make our programming jobs easier.

How To Use Pointers

A pointer to a variable is the address of a variable. Thus far in our discussion of variables, the standard variable types were found to occupy 8-, 16-, or 32 bits depending on the type. Pointers are not types in the sense of variables. Pointers might occupy some number of bits, and their size is implementation dependent. In fact, there are cases of pointers to like types having different sizes in the same pro­ gram depending on context.

If the variable px is a pointer to the type int and x is an int, px can be assigned the address of x by

px = &x;

The ampersand (&) notifies the compiler to use the address of x rather than the value of x in the above assignment. The reverse op­ eration to that above is

65

66 Chapter 2 Advanced C Topics

x = *px;

Here the asterisk (*) is a unary operator that applies to a pointer and directs the compiler to use the integer pointed to by the pointer px in the assignment. The unary * is referred to as the dereference operator. With these two operators, it is possible to move from vari­ able to pointer and back again with ease.

A pointer is identified as a pointer to a specific type by state­ ments like

int *px,*pa; long *pz; float *pm;

In the above declarations, each of the variables preceded by the unary asterisk identifies a pointer to the declared type. The pointers px and pa are the addresses of integers. pz is the address of a long, and pm is the address of a floating point number. Always remember: if pk is a pointer to an integer, *pk is an integer. Therefore, the declarations in the above examples are correct and do define ints, longs and floats. However, when the compiler encounters the statement

int *pi;

it provides memory space for a pointer to the type int, but it does not provide any memory for the int itself. Suppose that a program has the following declaration:

int m,n; int *pm;

The statement

m = 10; pm = &m;

will assign the value 10 to m and put its address into the pointer pm. If we then make the assignment

n = *pm;

the variable n will have a value 10.

Another interesting fact about pointers is shown in the following example:

Pointers 67

int able[10]; int *p;

After it has been properly declared, when the name able is invoked without the square brackets, the name is a pointer to the first location in the array. So the following two statements are equivalent:

p = &able[0];

and

p = able;

Of course, the nth element of the array may be addressed by

p = &able[n];

The unary pointer operators have higher precedence than the arith­ metic operators. Therefore,

*pi = *pi + 10; y = *pi + 1; *pi += 1;

all result in the integers being altered rather than the pointers. In the first case, the integer pointed to by pi will be increased by 10. In the second case, y will be replaced by one more than the integer pointed to by pi. Finally, the integer pointed to by pi will be increased by 1. The statement

++*pi;

causes the integer pointed to by pi to be increased by 1. Both ++ and the unary * associate from right to left, so in the following case

*pi++;

the pointer pi is incremented after the dereference operator is applied. Therefore, the pointer is incremented in this case, and the integer *pi remains unaltered. If you wish to post-increment the integer *pi, use

(*pi)++;

At times, it is necessary to pre-increment the pointer before the dereference. In these cases, use

*++pi;

68 Chapter 2 Advanced C Topics

Pointers work with arrays. Suppose that you have

int *pa; int a[20];

As we have already seen,

pa = a;

assigns the address of the first entry in the array to the pointer pa much the same as

pa = &a[0];

does. To increment the pointer to the next location in the array, you may use

pa++;

or

pa = pa + 1;

In any case, the pointer that is 1 larger than pa points to the next element in the array. This operation is true regardless of the type that pa points to. If pa points at an integer, pa++ points to the next integer. If pa points at a long, pa++ points to the next long. If pa points to a long double, pa++ points to the next long double. In fact, if pa points to an array of arrays, pa++ points to the next array in the array of arrays. C automatically takes care of knowing the number that must be added to a pointer to point to the next element in an array.

In the above case, since a is an array of 20 integers, and pa points to the first entry in the array, it follows that pa+1 points to the second element in the array, and pa+2 points to the third ele­ ment. Similarly, *(pa+1) is the second element in the array and *(pa+2) is the third element.

There is a set of arithmetic that can be applied to pointers. This arithmetic is different from the normal arithmetic that can be applied to numbers. In all cases, the arithmetic applies to pointers of like types pointing to within the same array only. When these conditions are met, the following arithmetic operations can be completed:

Pointers can be compared. Two pointers to like types in the same array can be compared in a C logical expression.

Pointers 69

Pointers can be subtracted. Two pointers to like types in the same array can be subtracted with a C arithmetic expression. The result of the subtraction will be the number of elements between the two pointers, not the difference in the values of the pointers.

Pointers can be incremented and decremented. A pointer into an array can be either incremented or decremented. The result will be a pointer that points to an adjacent element in the array.

Pointers can be assigned. A pointer can be assigned to another pointer of the same type.

Pointers cannot be added, multiplied, divided, masked, shifted, or assigned a pointer value of an unlike type. Pointers can be as­ signed to another pointer of the type void*.

The name of an array is a pointer to the first element in this array. This type of variable occupies a special place in C. The name can always be used as a pointer, and assigned to another pointer, but it cannot be assigned to. Any attempt to assign a new value to the array name would upset the beginning of the array to the program.

Therefore, an array name as a pointer can be used as a value on the right side of an assignment equal sign, but it cannot be used on the left side of the equal sign. C variables are broken into the types rvalue and lvalue. All C variables, with the exception of the names of functions and arrays, are both rvalues and lvalues. They can be used on either side of an equal sign in an assignment statement. Function names and array names are rvalues and can be used only on the right side of the equal sign in an assignment statement. Variables declared as constants and constants created by the #define statement are also rvalues.

The type void* has a special meaning when applied to pointers. A void pointer (sometimes referred to as a generic pointer) is a pointer that does not point at any specific type. Some functions will return void pointers, and to use these pointers, they must be cast onto the type that they represent. Therefore, a pointer of the type void* can be assigned the value of a typed pointer. However, unless the void* pointer is cast onto the specified type, the increment, decrement, subtraction, and so forth will not work. A void pointer can be used in expressions, but it is impossible to alter its value.

70 Chapter 2 Advanced C Topics

Pointers and Function Arguments

Values passed to functions as arguments are copies of the real values. The data to be passed to a function are pushed on the stack or saved in registers prior to the function call. Therefore, if the function should modify any of the arguments, this modification would not propagate to the calling function. Therefore, a function like

void swap(int x, int y)

{

int temp;

temp = x; x = y;

y = temp;

}

does nothing but swap two passed values, and those values are never returned to the calling program. This performance is good as well as bad. The bad situation is shown above when the function simply does not work as expected. The good side is that it is not easy to inadvertently change variable values in the calling program. The use of pointers permits this problem to be avoided. The technique is called passing parameters by reference. Consider the following function:

void swap(int* px, int* py)

{

int temp;

temp = *px; *px = *py; *py = temp;

}

Here the integers pointed to by px and py are swapped. These integers are the values in the calling program. The pointer values in the calling program are unaltered.

C makes extensive use of passing parameters by reference. Re­ call the first program in Chapter 1. That program has the line

printf(“Microcontrollers run the world!\n”);

Pointers 71

We now know that the string “Microcontrollers run the world!\n” is nothing more than a character array terminated by a zero. The compiler creates that array in the memory of the program. That array is not passed to printf. A pointer to that array is passed to printf. Then printf prints the characters to the standard out­ put and increments through the array until it finds the 0 terminator. Then it quits. In other instances, arguments like s[] were used. In these cases, C automatically knows to pass a pointer to the array. It is interesting. If you have an array int s[nn] and pass that array to a function as s, you can use an argument int* p in the function and then in the function body deal with the array p[]. The C language is extremely flexible in the use of pointers. An example of this type of operation is as follows:

/* Count the characters in a string */

#include <stdio.h>

int strlen(char *);

int main(void)

{

int n,s[80];

fgets(s,80,stdin);

n=strlen(s);

printf(“The string length = %d\n”,n); return 0;

}

int strlen( char *p)

{

int i=0;

while(p[i]!=0)

i++; return i;

}

72 Chapter 2 Advanced C Topics

The above version of strlen() will return the number of char­ acters in the string not including the terminating 0. The function gets() is a standard function that retrieves a character string from the keyboard. The prototype for this function is contained in stdio.h. It is interesting to note that the function strlen() can be altered in several ways to make the function simpler. The index i in the function can be post-incremented in the argument so that it is not necessary to have the statement following the while(). This function will be

int strlen(char *p)

{

int i=0;

while(p[i++]!=0); return i;

}

Because the while() argument is evaluated as a logical state­ ment, any value other than 0 will be considered TRUE. Therefore,

int strlen(char *p)

{

int i=0;

while(p[i++]); return i;

}

provides exactly the same performance.Yet another viewpoint is found by

int strlen(char* p)

{

int i;

for(i=0;*p++;i++); return i;

}

Each of the above functions using strlen() show a different operation involving passing arguments by reference.

Let us examine another use of passing arguments by pointers.

Pointers 73

The following function compares two strings. It returns a negative number if the string p is less than the string q, 0 if the two strings are equal, and a positive number if the string p is greater than the string q.

int strcomp(char* p, char* q)

{

while( *p++ == *q++)

if(*(p-1) == ‘\0’) /* The strings are equal */ return 0; /* zero */

return *(p-1) - *(q-1); /* The strings are unequal, return the correct value*/

}

Here the while() statement will examine each character in the string until the argument is not equal. Note, that the pointers p and q are incremented after they are used. The test for equality will not occur until after the pointers are incremented, so it is necessary to decrement the pointer p to determine if the last equal value in the two strings is a zero character. If the last value is a zero, the two strings are equal, and a 0 is returned. If the last tested character is not a zero, the two strings are not equal and the difference between the last character in p and the last character in q is returned. This choice will give the correct sign needed to meet the function specification.

Another approach can be used that eliminates the increments and decrements within the program and confines them to the argument of a for construct. Consider

int strcomp(char* p, char* q)

{

for( ; *p==*q ; p++, q++) if( *p == ‘\0’)

return 0; return *p - *q;

}

The pointers p and q are both incremented after the test in the if statement. Therefore, the pointer values are correct for the if statement as well as for calculation of the return value when the strings are not equal.

74 Chapter 2 Advanced C Topics

C is completely unforgiving if you exceed the boundaries of the array in your calculations. C does not have intrinsic boundary checks, and it is possible to increment pointers right off the end of an array. For that matter, the array index can be decremented to addresses below the beginning of the array. When a program makes such a mistake, it will destroy other data, perhaps destroy the program be­ ing executed, or in the worse case, the program can crash the system. The simple single-tasking computers that run MS-DOS or similar operating systems have no memory protection feature that protects one task from another. In such cases, a program that runs wild and overwrites memory not assigned to it can destroy other tasks that are loaded in memory whether they are running or not.

EXERCISES

1.Write several versions of a function that copies one string into another.

2.Write a function that concatenates two strings. Concatenation of two strings means that one string will be written at the end of the other. Write this function with and without the use of pointers. What is the advantage of the pointer version of the function?

Let us consider a problem that will be analyzed in more detail later. Suppose that a function is needed that will sort the contents of an array into ascending (or descending) order. There are several sort functions, and each has its own set of advantages and disadvantages. The simplest and probably most intuitive is called the bubble sort. In a bubble sort, a swap flag is reset. The first entry in the array is compared with the second, and if they are in the wrong order, they are swapped. Then the second entry is compared with the third, and they are swapped or not. If a swap ever occurs, the swap flag is set. The elements of the array are successively compared and swapped until all of the elements in the array have been compared. If a swap has occurred during the scan of the array, the swap flag is reset, and the whole process is repeated. This process repeats until the scan of the array causes no swaps to occur. At that time, the array is in order.

If the array contains n elements, this approach requires on the order of n squared compares and swaps. For large arrays, the time to sort an array with a bubble sort is inordinate. Another approach was

Pointers 75

discovered by D. L. Shell. The Shell sort allows the array to be sorted with n times the logarithm base two of n. This difference can be substantial. For example, if the array contains 1000 ele­ ments, the bubble sort will require on the order of 1,000,000 compares and swaps while the Shell sort will need only 10,000. Therefore, the bubble sort should not be used to sort large arrays.

Another sort technique that is used was discovered by C. A. R. Hoare. This technique is called the quick sort. It uses a recursive approach to accomplish the sort, and it also requires n log base 2 of n compares and swaps. The shell sort and the quick sort usually require about the same time to accomplish the sort. We will dem­ onstrate a shell sort now.

The code for a shell sort follows. Note that extensive use of pointers is used in this version of the shell sort.

/* shell_sort(): sort the contents of the array *v into ascending order */

void shell_sort(int* v, int n)

{

int gap, i, j, temp;

for( gap = n/2; gap > 0; gap /= 2) for( i=gap; i < n; i++)

for(j=i-gap;j>=0&&*(v+j)>*(v+j+gap); j -= gap)

{

temp = *(v+j); *(v+j) = *(v+j+gap); *(v+j+gap) = *(v+j);

}

}

The shell sort receives as arguments a pointer to an array of inte­ gers and an integer n which is the length of the array.

This sort works by successively dividing the array into subarrays. The first operation divides the whole array into two parts; the second divides each of the two subarrays into two new subarrays, for a total of four arrays; and so forth.

76 Chapter 2 Advanced C Topics

The corresponding elements in each subarray are compared, and if they are out of order, they are swapped. At the close of this opera­ tion, a new set of subarrays are created, and the process is repeated over these subarrays. Eventually, the gap between elements in the subarrays will be reduced to one, and the array contents will be sorted.

The outer for loop above controls the splitting of the arrays into the subarrays. The second loop steps along the array pairs. The in­ nermost loop successively compares the elements that are separated by gaps in the subarrays. If elements are found that are out of order, they are reversed or swapped in the array.

EXERCISES

1.Restate the shell sort above to use arrays rather than pointers to arrays.

2.Write a program that reads in characters from the input stream and record the number of occurrences of each character. Calculate the percentage occurrence of each character, and print out the result in ascending order of percentage of occurrence.

Functions can return pointers. For example, prototype to a func­ tion that returns a pointer is:

int *able(char* );

Here able returns a pointer to an integer.

In C, a NULL pointer is never used. A NULL pointer implies that something is to be stored at the address zero. This address is never avail­ able for data storage, so no function can return a valid NULL pointer. The NULL pointer can be used as a flag or an error return. The pro­ grammer should never allow a NULL pointer to be dereferenced, which implies that data are read or stored at 0.

If C will support a pointer to a variable, it requires but little imagi­ nation to reason that C will support pointers to pointers. In fact, there is no practical limit in the language to the depth of dereference C will support. C will also allow arrays of pointers, and pointers to functions. (We discussed pointers to arrays in the preceding section.) An array of pointers can be very useful when needed. A most obvi­ ous use for an array of pointers is to read the contents of a command line to a program. So far in our discussion of programs, there have been no provisions for reading the content of a command line that is

Pointers 77

written to the screen when the program is executed. A command line can be read by the program. The definition of the program name main when extended to read in a command line is as follows

void main ( int argc, char *argv[])

The integer variable argc is the number of entries on the command line. The array of pointers to the type char argv[] contains pointers to strings. When entering arguments onto the command line, they must be separated by spaces. The first string pointed to by argv[0] is the name of the program from the command line. The successive pointer values point to additional character strings. These strings are each 0 terminated, and they point to the successive entries on the command line. The value of argc is the total number of command line entries including the program name. It must be remembered that each entry in argv[] is a pointer to a string. Therefore, if a number is entered on the command line, it must be converted from a string to an integer, or floating point number, prior to its use in the program. Let us see how this concept can be used. Earlier, we wrote a function to calculate a Fibonacci number. Let’s use this function in a program in which the argument for the Fibonacci calculation is read in from the command line:

#include <stdio.h> #include <stdlib.h>

long fib( int ); /* Fibonacci number function prototype */

int main( int argc, char* argv[] )

{

int i;

i = atoi(argv[1]);

printf(“The Fibonacci number of %d = %ld\n”, i, fib(i));

return 0;

}

We will not repeat the code for fib(i). A new header file, stdlib.h, is included with this program. The function prototype for atoi() is contained within this header file. The standard com­ mand line arguments are used in the call to main(). The line

78 Chapter 2 Advanced C Topics

i = atoi(argv[1]);

causes the function atoi—ASCII to integer conversion—to be ex­ ecuted with a pointer as an argument. This pointer points to the first argument following the program name on the command line. In this case, it will be pointing to an ASCII string that contains the number to be used as an argument for the fib() call. This string must be converted to an integer before fib() can operate on it, which is exactly what the atoi() function accomplishes. The final line in this program prints out the result of the calculation.

Another example of use of the command line arguments is to print out the command line. The following program will accomplish this task.

#include <stdio.h>

int main( int argc, char* argv[] )

{

int i;

for(i=0; argc--; i++) printf(“%s “,argv[i]); printf(“\n”);

return 0;

}

The arguments to main() are the same as before. This program enters a for loop that initializes i to zero. It decrements argc each time it tests its value, and executes until the loop in which argc is decremented to 0. The printf call

printf(“%s “,argv[i]);

prints out the string to which argv[i] points. Notice the space in the string “%s “. This space will force a space between each argument as it is printed. The program is written so that there are no new line charac­ ters printed. Arguments will all be on one line, and they will each be separated by a space. The printf() statement after execution of the for() loop will print out a single new line so that the cursor will return to the next line after the program is executed.

Command line entry is but a simple example of use of arrays of pointers. Another area in which arrays to pointers are needed is in order­ ing strings of data. For example, it is possible to collect a large number of words in memory, say from an input stream. Suppose that it is needed to alphabetize these words. We saw earlier, that the shell sort will order

Pointers 79

the contents of an array, so it might be possible to simply modify the shell sort to do this job.

First, a comparison is needed that will determine if the lexical value of a one word is smaller, equal, or larger than that of another word. Such a compare routine was outlined above. Second, a swap routine that will swap the words that are in the wrong order. Here is a case where an array of pointers can be quite useful. Assume that the program that reads in the data will put each word into a separate memory location and keep an array of pointers to the beginning of each word rather than just the array of the words themselves. Then in the shell sort, when a swap is required, rather than swapping the words, swap the pointers in the array. Swap routines that swap pointers in the array are very easy to implement. On the other hand, swap routines to swap two strings in memory are diffi­ cult and slow. Therefore, we can create a sort routine that is much more efficient if we use an array of pointers rather than an array of strings.

/* shell_sort(): sort the contents of the array char* v[] into ascending order */

#include <string.h>

void shell_sort(char* v[], int n)

{

int gap, i, j; char* temp;

for( gap = n/2; gap > 0; gap /= 2) for( i=gap; i < n; i++)

for( j=i-gap; j>=0 && strcmp(v[j],v[j+gap]); j -= gap)

{

temp = v[j]; v[j] = v[j+gap]; v[j+gap] = v[j];

}

}

Here the strcmp() routine is used to determine if a swap is needed. strcmp() is identified in the header file string.h. When needed, the contents of the array of pointers to the beginning of the words is swapped rather than swapping the words themselves.