Functions, Arrays, and Pointers

I l @ ve RuBoard

Functions, Arrays, and Pointers

Suppose you want to write a function that operates on an array. For instance, suppose you want a function that returns the sum of the elements of an array. Listing 10.7 shows a program that does this. To point out an interesting fact about array arguments, the program also prints the size of the relevant arrays. (Recall that some pre-ANSI compilers require %ld for printing sizeof quantities .)

Listing 10.7 The sum_arr1.c program.
 /* sum_arr1.c -- sums the elements of an array */ #include <stdio.h> #define SIZE 10 long sum(int ar[], int n); int main(void) {     int marbles[SIZE] = {20,10,5,39,4,16,19,26,31,20};     long answer;     answer = sum(marbles, SIZE);     printf("The total number of marbles is %ld.\n", answer);     printf("The size of marbles is %d bytes.\n",           sizeof marbles);     return 0; } long sum(int ar[], int n)     /* how big an array? */ {     int i;     long total = 0;     for( i = 0; i < n; i++)         total += ar[i];     printf("The size of ar is %d bytes.\n", sizeof ar);     return total; } 

Recall that the += operator adds the value of the operand on its right to the operand on its left. Therefore, total is a running sum of the array elements. The output on our system looks like this:

 The size of ar is 4 bytes. The total number of marbles is 190. The size of marbles is 40 bytes. 

Array Names as Arguments

Well, the function does successfully sum the number of marbles, but what is ar ? From the function argument declarations, you might expect that ar is a new array into which are copied the values found in marbles ”the actual argument to the function. After all, the formal argument n is assigned the value of its corresponding actual argument. Yet ar is only 4 bytes, so it couldn't be assigned the 40 bytes of data in marbles . Therefore, ar is not an array of 10 int s.

The reason that ar is not an array is that C does not allow arrays to be passed as function arguments. Look at this function call:

 sum(marbles, SIZE); 

The first argument is marbles , the name of an array, and the name of an array, recall, is the address of the first element of the array. That is, the identifier marbles is not an array; it is the address of an int , marbles[0] , so the function call sum(marbles, SIZE) is the same as sum(&marbles[0], SIZE) and passes two numbers : an address and an integer. This means that the formal arguments to sum() should be a pointer-to- int and an int . The function heading should look like this:

 int sum(int *ar, int n) 

Indeed, if you replace the heading in Listing 10.7 with this heading, the program works exactly the same as before. When you declare a formal argument (and only when you declare a formal argument) in C, the forms

 int *ar   /* formal.argument */ 

and

 int ar[]  /* formal argument */ 

are exactly equivalent. Both state that ar is a pointer-to- int . That is why sizeof ar is 4 ”our system uses 4-byte pointers.

If ar is a pointer, why can you use the expression ar[i] in sum() ? In C, remember, ar[i] is the same as *(ar + i) . Both represent the value stored at address ar +i . This is true if ar is the name of an array, and it is true if ar is the name of a pointer variable. Therefore, you can use pointer notation to process an array, and you can use array notation with a pointer variable. In this instance, ar starts out pointing to the first element of the marbles array. Increasing i then makes ar + i point to each element in turn . Because you also pass the size of the array, the sum() function knows when the end of the array is reached.

In short, when you use an array name as a function argument, you pass the address of the first element of the array to the function. The function then uses a pointer set to that address to access the original array in the calling program.

Incidentally, we designed the function to use an argument representing the array size, which enables us to use the same function for differently sized arrays. Note that the only way the function can know the array size is if we tell it. As the example shows, applying the sizeof operator to an array name yields the size of an array, but applying sizeof to a pointer variable yields the size of the pointer, not the size of the object that is pointed to. Another way to indicate the size is to build it into the function. That is, instead of using the n argument in sum() , we could have made the loop test be i < 10 . However, such a function would be limited to working with 10-element arrays.

Using Pointer Arguments

The sum() function used two items of information to describe an array. First, a pointer identified the beginning of the array and the type of information stored. Then an integer indicated how many elements to process. Another way to describe the array is by passing two pointers, with the first indicating where the array starts (as before) and the second where the array ends. Listing 10.8 illustrates this approach. It also uses the fact that a pointer argument is a variable; that means instead of using an index to indicate which element in an array to access, the function can alter the value of the pointer itself, making it point to each array element in turn. Listing 10.8 also illustrates this technique.

Listing 10.8 The sum_arr2.c program.
 /* sum_arr2.c -- sums the elements of an array */ #include <stdio.h> #define SIZE 10 long sump(int * start, int * end); int main(void) {   int marbles[SIZE] = {20,10,5,39,4,16,19,26,31,20};   long answer;   answer = sump(marbles, marbles + SIZE);   printf("The total number of marbles is %ld.\n", answer);   return 0; } /* use pointer arithmetic   */ long sump(int * start, int * end) {   long total = 0;   while (start < end)   {     total += *start;  /* add value to total              */     start++;          /* advance pointer to next element */   }   return total; } 

The pointer start begins by pointing to the first element of marbles , so the assignment expression total +=*start adds the value of the first element (20) to total . Then the expression start++ increments the pointer variable start so that it points to the next element in the array. Because start points to type int , C increments the value of start by the size of int .

Note that the sump() ·function uses a different method from sum() to end the summation loop. The sum() function uses the number of elements as a second argument, and the loop uses that value as part of the loop test:

 for( i = 0; i < n; i++) 

The sump() function, however, uses a second pointer to end the loop:

 while (start < end) 

Because the test is for inequality, the last element processed is the one just before the element pointed to by end . This mean that end actually points to the location after the final element in the array. C guarantees that when it allocates space for an array, a pointer to the first location after the end of the array is a valid pointer. That makes constructions like this one valid, for the final value start gets in the loop is end . Note that using this "past-the-end" pointer makes the function call neat:

 answer = sump(marbles, marbles + SIZE); 

Because indexing starts at , marbles + SIZE points to the next element after the end. If end pointed to the last element instead of to one past the end, you would have to use this code instead:

 answer = sump(marbles, marbles + SIZE - 1); 

Not only is this code less elegant in appearance, it's harder to remember, hence more likely to lead to programming errors. By the way, although C guarantees that the pointer marbles + SIZE is a valid pointer, it makes no guarantees about marbles[SIZE] , the value stored at that location.

You can also condense the body of the loop to one line:

 total += *start++; 

The unary operators * and ++ have the same precedence but associate from right to left. This means the ++ applies to start , not to *start . That is, the pointer is incremented, not the value pointed to. The use of the postfix form ( start++ rather than ++start ) means that the pointer is not incremented until after the pointed-to value is added to total . If the program used *++start , the order would be to increment the pointer, then use the value pointed to. If the program used (*start)++ , however, it would use the value of start and then increment the value, not the pointer. That would leave the pointer pointing to the same element, but the element would contain a new number. Although the *start++ notation is commonly used, you might prefer to use *(start++) for clarity. Listing 10.9 illustrates these niceties of precedence.

Listing 10.9 The order.c program.
 /* order.c -- precedence in pointer operations */ #include <stdio.h> int data[2] = {100, 300}; int moredata[2] = {200, 400}; int main(void) {   int * p1, * p2, * p3;   p1 = p2 = data;   p3 = moredata;   printf("*p1++ = %d, *++p2 = %d, (*p3)++ = %d\n",           *p1++, *++p2, (*p3)++);   printf("  *p1 = %d,   *p2 = %d,     *p3 = %d\n",           *p1, *p2, *p3);   return 0; } 

Here is its output:

 *p1++ = 100, *++p2 = 300, (*p3)++ = 200   *p1 = 300,   *p2 = 300,     *p3 = 201 

The only operation that altered an array value is (*p3)++ . The other two operations caused p1 and p2 to advance to point to the next array element.

Comment: Pointers and Arrays

As you have seen, functions that process arrays actually use pointers as arguments, but you do have a choice between array notation and pointer notation for writing array-processing functions. Using array notation, as in Listing 10.7, makes it more obvious that the function is working with arrays. Also, array notation has a more familiar look to programmers versed in other languages, such as FORTRAN, Pascal, Modula-2, or BASIC. Other programmers may be more accustomed to working with pointers and might find the pointer notation more natural.

As far as C goes, the two notations are equivalent in meaning. Pointer notation, particularly when used with the increment operator, is closer to machine language and, with some compilers, leads to more efficient code. Many programmers, however, believe that the programmer's main concerns should be correctness and clarity and that code optimization should be left to the compiler.

I l @ ve RuBoard


C++ Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 314
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net