One of the most common uses of pointers in C is as pointers to arrays. The main reasons for using pointers to arrays are ones of notational convenience and of program efficiency. Pointers to arrays generally result in code that uses less memory and executes faster. The reason for this will become apparent through our discussions in this section.
If you have an array of 100 integers called values, you can define a pointer called valuesPtr, which can be used to access the integers contained in this array with the statement
int *valuesPtr;
When you define a pointer that is used to point to the elements of an array, you don’t designate the pointer as type “pointer to array”; rather, you designate the pointer as pointing to the type of element that is contained in the array.
If you have an array of characters called text, you could similarly define a pointer to be used to point to elements in text with the statement
char *textPtr;
To set valuesPtr to point to the first element in the values array, you simply write
valuesPtr = values;
The address operator is not used in this case because the C compiler treats the appear- ance of an array name without a subscript as a pointer to the array. Therefore, simply specifying values without a subscript has the effect of producing a pointer to the first element of values (see Figure 11.9).
An equivalent way of producing a pointer to the start of values is to apply the address operator to the first element of the array. Thus, the statement
valuesPtr = &values[0];
can be used to serve the same purpose as placing a pointer to the first element of values in the pointer variable valuesPtr.
To set textPtr to point to the first character inside the text array, either the statement
textPtr = text;
or
textPtr = &text[0];
can be used. Whichever statement you choose to use is strictly a matter of taste.
The real power of using pointers to arrays comes into play when you want to sequence through the elements of an array. If valuesPtr is as previously defined and is set pointing to the first element of values, the expression
*valuesPtr
can be used to access the first integer of the values array, that is, values[0]. To refer- ence values[3] through the valuesPtr variable, you can add 3 to valuesPtr and then apply the indirection operator:
*(valuesPtr + 3)
In general, the expression
*(valuesPtr + i)
can be used to access the value contained in values[i].
So, to set values[10] to 27, you could obviously write the expression
values[10] = 27;
or, using valuesPtr, you could write
*(valuesPtr + 10) = 27;
To set valuesPtr to point to the second element of the values array, you can apply the address operator to values[1] and assign the result to valuesPtr:
valuesPtr = &values[1];
If valuesPtr points to values[0], you can set it to point to values[1] by simply adding 1 to the value of valuesPtr:
valuesPtr += 1;
This is a perfectly valid expression in C and can be used for pointers to any data type.
So, in general, if a is an array of elements of type x, px is of type “pointer to x,” and i and n are integer constants or variables, the statement
px = a;
sets px to point to the first element of a, and the expression
*(px + i)
subsequently references the value contained in a[i]. Furthermore, the statement
px += n;
sets px to point n elements farther in the array, no matter what type of element is contained in the array.
The increment and decrement operators ++ and — are particularly handy when deal- ing with pointers. Applying the increment operator to a pointer has the same effect as adding one to the pointer, while applying the decrement operator has the same effect as subtracting one from the pointer. So, if textPtr is defined as a char pointer and is set pointing to the beginning of an array of chars called text, the statement
++textPtr;
sets textPtr pointing to the next character in text, which is text[1]. In a similar fash- ion, the statement
–textPtr;
sets textPtr pointing to the previous character in text, assuming, of course, that textPtr was not pointing to the beginning of text prior to the execution of this state- ment.
It is perfectly valid to compare two pointer variables in C. This is particularly useful when comparing two pointers in the same array. For example, you can test the pointer valuesPtr to see if it points past the end of an array containing 100 elements by com- paring it to a pointer to the last element in the array. So, the expression
valuesPtr > &values[99]
is TRUE (nonzero) if valuesPtr is pointing past the last element in the values array, and is FALSE (zero) otherwise. Recall from previous discussions that you can replace the preceding expression with its equivalent
valuesPtr > values + 99
because values used without a subscript is a pointer to the beginning of the values
array. (Remember, it’s the same as writing &values[0].)
Program 11.11 illustrates pointers to arrays. The arraySum function calculates the sum of the elements contained in an array of integers.
Program 11.11 Working with Pointers to Arrays
// Function to sum the elements of an integer array
#include <stdio.h>
int arraySum (int array[], const int n)
{
int sum = 0, *ptr;
int * const arrayEnd = array + n;
for ( ptr = array; ptr < arrayEnd; ++ptr )
sum += *ptr;
return sum;
}
int main (void)
{
int arraySum (int array[], const int n);
int values[10] = { 3, 7, -9, 3, 6, -1, 7, 9, 1, -5 };
printf (“The sum is %i\n”, arraySum (values, 10));
return 0;
}
Program 11.11 Output
The sum is 21
Inside the arraySum function, the constant integer pointer arrayEnd is defined and set pointing immediately after the last element of array.A for loop is then set up to sequence through the elements of array. The value of ptr is set to point to the begin- ning of array when the loop is entered. Each time through the loop, the element of array pointed to by ptr is added into sum. The value of ptr is then incremented by the for loop to set it pointing to the next element in array.When ptr points past the end of array, the for loop is exited, and the value of sum is returned to the calling routine.
1. A Slight Digression About Program Optimization
It is pointed out that the local variable arrayEnd was not actually needed by the func- tion because you could have explicitly compared the value of ptr to the end of the array inside the for loop:
for ( …; pointer <= array + n; … )
The sole motivation for using arrayEnd was one of optimization. Each time through the for loop, the looping conditions are evaluated. Because the expression array + n is never changed from within the loop, its value is constant throughout the execution of the for loop. By evaluating it once before the loop is entered, you save the time that would otherwise be spent reevaluating this expression each time through the loop. Although there is virtually no savings in time for a 10-element array, especially if the arraySum function is called only once by the program, there could be a more substantial savings if this function were heavily used by a program for summing large-sized arrays, for example.
The other issue to be discussed about program optimization concerns the very use of pointers themselves in a program. In the arraySum function discussed earlier, the expression *ptr is used inside the for loop to access the elements in the array. Formerly, you would have written your arraySum function with a for loop that used an index variable, such as i, and then would have added the value of array[i] into sum inside the loop. In general, the process of indexing an array takes more time to execute than does the process of accessing the contents of a pointer. In fact, this is one of the main reasons why pointers are used to access the elements of an array—the code that is generated is generally more efficient. Of course, if access to the array is not generally sequential, pointers accomplish nothing, as far as this issue is concerned, because the expression *(pointer + j) takes just as long to execute as does the expression array[j].
2. Is It an Array or Is It a Pointer?
Recall that to pass an array to a function, you simply specify the name of the array, as you did previously with the call to the arraySum function.You should also remember that to produce a pointer to an array, you need only specify the name of the array. This implies that in the call to the arraySum function, what was passed to the function was actually a pointer to the array values. This is precisely the case and explains why you are able to change the elements of an array from within a function.
But if it is indeed the case that a pointer to the array is passed to the function, then you might wonder why the formal parameter inside the function isn’t declared to be a pointer. In other words, in the declaration of array in the arraySum function, why isn’t the declaration
int *array;
used? Shouldn’t all references to an array from within a function be made using pointer variables?
To answer these questions, recall the previous discussion about pointers and arrays. As mentioned, if valuesPtr points to the same type of element as contained in an array called values, the expression *(valuesPtr + i) is in all ways equivalent to the expres- sion values[i], assuming that valuesPtr has been set to point to the beginning of values.What follows from this is that you also can use the expression *(values + i) to reference the ith element of the array values, and, in general, if x is an array of any type, the expression x[i] can always be equivalently expressed in C as *(x + i).
As you can see, pointers and arrays are intimately related in C, and this is why you can declare array to be of type “array of ints” inside the arraySum function or to be of type “pointer to int.” Either declaration works just fine in the preceding program—try it and see.
If you are going to be using index numbers to reference the elements of an array that is passed to a function, declare the corresponding formal parameter to be an array. This more correctly reflects the use of the array by the function. Similarly, if you are using the argument as a pointer to the array, declare it to be of type pointer.
Realizing now that you could have declared array to be an int pointer in the pre- ceding program example, and then could have subsequently used it as such, you can eliminate the variable ptr from the function and use array instead, as shown in Program 11.12.
Program 11.12 Summing the Elements of an Array
// Function to sum the elements of an integer array Ver. 2
#include <stdio.h>
int arraySum (int *array, const int n)
{
int sum = 0;
int * const arrayEnd = array + n;
for ( ; array < arrayEnd; ++array )
sum += *array;
return sum;
}
int main (void)
{
int arraySum (int *array, const int n);
int values[10] = { 3, 7, -9, 3, 6, -1, 7, 9, 1, -5 };
printf (“The sum is %i\n”, arraySum (values, 10));
return 0;
}
Program 11.12 Output
The sum is 21
The program is fairly self-explanatory. The first expression inside the for loop was omit- ted because no value had to be initialized before the loop was started. One point worth repeating is that when the arraySum function is called, a pointer to the values array is passed, where it is called array inside the function. Changes to the value of array (as opposed to the values referenced by array) do not in any way affect the contents of the values array. So, the increment operator that is applied to array is just incrementing a pointer to the array values, and not affecting its contents. (Of course, you know that you can change values in the array if you want to, simply by assigning values to the ele-ments referenced by the pointer.)
3. Pointers to Character Strings
One of the most common applications of using a pointer to an array is as a pointer to a character string. The reasons are ones of notational convenience and efficiency. To show how easily pointers to character strings can be used, write a function called copyString to copy one string into another. If you write this function using normal array indexing methods, the function might be coded as follows:
void copyString (char to[], char from[])
{
int i;
for ( i = 0; from[i] != ‘\0’; ++i )
to[i] = from[i];
to[i] = ‘\0’;
}
The for loop is exited before the null character is copied into the to array, thus explain- ing the need for the last statement in the function.
If you write copyString using pointers, you no longer need the index variable i.A pointer version is shown in Program 11.13.
Program 11.13 Pointer Version of copyString
#include <stdio.h>
void copyString (char *to, char *from)
{
for ( ; *from != ‘\0’; ++from, ++to )
*to = *from;
*to = ‘\0’;
}
int main (void)
{
void copyString (char *to, char *from);
char string1[] = “A string to be copied.”;
char string2[50];
copyString (string2, string1);
printf (“%s\n”, string2);
copyString (string2, “So is this.”);
printf (“%s\n”, string2);
return 0;
}
Program 11.13 Output
A string to be copied.
So is this.
The copyString function defines the two formal parameters to and from as character pointers and not as character arrays as was done in the previous version of copyString. This reflects how these two variables are used by the function.
A for loop is then entered (with no initial conditions) to copy the string pointed to by from into the string pointed to by to. Each time through the loop, the from and to pointers are each incremented by one. This sets the from pointer pointing to the next character that is to be copied from the source string and sets the to pointer pointing to the location in the destination string where the next character is to be stored.
When the from pointer points to the null character, the for loop is exited. The func- tion then places the null character at the end of the destination string.
In the main routine, the copyString function is called twice, the first time to copy the contents of string1 into string2, and the second time to copy the contents of the constant character string “So is this.” into string2.
4. Constant Character Strings and Pointers
The fact that the call
copyString (string2, “So is this.”);
works in the previous program implies that when a constant character string is passed as an argument to a function, what is actually passed is a pointer to that character string. Not only is this true in this case, but it also can be generalized by saying that whenever a constant character string is used in C, it is a pointer to that character string that is pro- duced. So, if textPtr is declared to be a character pointer, as in
char *textPtr;
then the statement
textPtr = “A character string.”;
assigns to textPtr a pointer to the constant character string “A character string.” Be careful to make the distinction here between character pointers and character arrays, as the type of assignment just shown is not valid with a character array. So, for example, if text is defined instead to be an array of chars, with a statement such as
char text[80];
then you could not write a statement such as
text = “This is not valid.”;
The only time that C lets you get away with performing this type of assignment to a character array is when initializing it, as in
char text[80] = “This is okay.”;
Initializing the text array in this manner does not have the effect of storing a pointer to the character string “This is okay.” inside text, but rather the actual characters themselves inside corresponding elements of the text array.
If text is a character pointer, initializing text with the statement
char *text = “This is okay.”;
assigns to it a pointer to the character string “This is okay.”
As another example of the distinction between character strings and character string pointers, the following sets up an array called days, which contains pointers to the names of the days of the week.
char *days[] =
{ “Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday” };
The array days is defined to contain seven entries, each a pointer to a character string. So days[0] contains a pointer to the character string “Sunday”, days[1] contains a pointer to the string “Monday”, and so on (see Figure 11.10).You could display the name of the third weekday, for example, with the following statement:
printf (“%s\n”, days[3]);
5. The Increment and Decrement Operators Revisited
Up to this point, whenever you used the increment or decrement operator, it was the only operator that appeared in the expression. When you write the expression ++x, you know that this has the effect of adding 1 to the value of the variable x. And as you have just seen, if x is a pointer to an array, this has the effect of setting x to point to the next element of the array.
The increment and decrement operators can be used in expressions in which other operators also appear. In such cases, it becomes important to know more precisely how these operators work.
So far, when you used the increment and decrement operators, you always placed them before the variables that were being incremented or decremented. So, to increment a variable i, you simply wrote
++i;
Actually, it also is perfectly valid to place the increment operator after the variable, as fol- lows:
i++;
Both expressions are perfectly valid and both achieve the same result—namely, of incre- menting the value of i. In the first case, where the ++ is placed before its operand, the increment operation is more precisely identified as a preincrement. In the second case, where the ++ is placed after its operand, the operation is identified as a postincrement.
The same discussion applies to the decrement operator. So the statement
–i;
technically performs a predecrement of i, whereas the statement
i–;
performs a postdecrement of i. Both have the same net result of subtracting 1 from the value of i.
It is when the increment and decrement operators are used in more complex expres- sions that the distinction between the pre- and post- nature of these operators is realized.
Suppose you have two integers called i and j. If you set the value of i to 0 and then write the statement
j = ++i;
the value that gets assigned to j is 1, and not 0 as you might expect. In the case of the preincrement operator, the variable is incremented before its value is used in the expres- sion. So, in the preceding expression, the value of i is first incremented from 0 to 1 and then its value is assigned to j, as if the following two statements had been written instead:
++i;
j = i;
If you instead use the postincrement operator in the statement
j = i++;
then i is incremented after its value has been assigned to j. So, if i is 0 before the pre- ceding statement is executed, 0 is assigned to j and then i is incremented by 1, as if the statements
j = i;
++i;
were used instead. As another example, if i is equal to 1, then the statement
x = a[–i];
has the effect of assigning the value of a[0] to x because the variable i is decremented before its value is used to index into a. The statement
x = a[i–];
used instead has the effect of assigning the value of a[1] to x because i is decremented after its value has been used to index into a.
As a third example of the distinction between the pre- and post- increment and decrement operators, the function call
printf (“%i\n”, ++i);
increments i and then sends its value to the printf function, whereas the call
printf (“%i\n”, i++);
increments i after its value has been sent to the function. So, if i is equal to 100, the first printf call displays 101, whereas the second printf call displays 100. In either case, the value of i is equal to 101 after the statement has executed.
As a final example on this topic before presenting Program 11.14, if textPtr is a character pointer, the expression
*(++textPtr)
first increments textPtr and then fetches the character it points to, whereas the expression
*(textPtr++)
fetches the character pointed to by textPtr before its value is incremented. In either case, the parentheses are not required because the * and ++ operators have equal prece- dence but associate from right to left.
Now go back to the copyString function from Program 11.13 and rewrite it to incorporate the increment operations directly into the assignment statement.
Because the to and from pointers are incremented each time after the assignment statement inside the for loop is executed, they should be incorporated into the assign- ment statement as postincrement operations. The revised for loop of Program 11.13 then becomes
for ( ; *from != ‘\0’; )
*to++ = *from++;
Execution of the assignment statement inside the loop proceeds as follows. The character pointed to by from is retrieved and then from is incremented to point to the next char- acter in the source string. The referenced character is then stored inside the location pointed to by to, and then to is incremented to point to the next location in the desti- nation string.
Study the preceding assignment statement until you fully understand its operation. Statements of this type are so commonly used in C programs, it’s important that you understand it completely before continuing.
The preceding for statement hardly seems worthwhile because it has no initial expression and no looping expression. In fact, the logic would be better served when expressed in the form of a while loop. This has been done in Program 11.14. This pro- gram presents your new version of the copyString function. The while loop uses the fact that the null character is equal to the value 0, as is commonly done by experienced C programmers.
Program 11.14 Revised Version of the copyString Function
// Function to copy one string to another. Pointer Ver. 2
#include <stdio.h>
void copyString (char *to, char *from)
{
while ( *from )
*to++ = *from++;
*to = ‘\0’;
}
int main (void)
{
void copyString (char *to, char *from);
char string1[] = “A string to be copied.”;
char string2[50];
copyString (string2, string1);
printf (“%s\n”, string2);
copyString (string2, “So is this.”);
printf (“%s\n”, string2);
return 0;
}
Program 11.14 Output
A string to be copied.
So is this.
Source: Kochan Stephen G. (2004), Programming in C: A Complete Introduction to the C Programming Language, Sams; Subsequent edition.