Chapter 19
Deep into Pointer Land
In This Chapter
Using a pointer to display an array
Replacing array notation with pointers
Working with strings and pointers
Understanding arrays of pointers
Performing a string sort
Creating a function that eats pointers
Building functions that return pointers
It's easy to accept what a pointer does, to numbly nod your head, to repeat the mantra, "A pointer is a variable that contains a memory location." You can memorize the difference between pointer variable p
and pointer variable *p
. But to truly know the power of the pointer, you have to discover how it's fully exploited in the C language. You must eschew the old way of doing things and fully embrace pointers for the miraculous witchcraft they do.
Pointers and Arrays
Arrays in the C language are nothing but a kettle full of lies! Truly, they don’t exist. As you discover the power of the pointer, you come to accept that an array is merely a cleverly disguised pointer. Be prepared to feel betrayed.
Getting the address of an array
An array is a type of variable in C, one that you can examine for its size and address. Chapter 18 covers using the sizeof
operator on an array. Now you uncover the deep, dark secret of beholding an array's address.
The source code from Listing 19-1 shows a teensy program that declares an int
array and then displays that array's location in memory. Simple. (Well, it's simple if you've worked through Chapter 18.)
Listing 19-1: Where the Array Lurks
#include <stdio.h>
int main()
{
int array[5] = { 2, 3, 5, 7, 11 };
printf("'array' is at address %p\n",&array);
return(0);
}
Exercise 19-1: Type the source code from Listing 19-1 into your editor. Build and run the program.
Here’s the output I see:
'array' is at address 0028FF0C
Exercise 19-2: Duplicate Line 7 in the code to create a new Line 8, removing the ampersand:
printf("'array' is at address %p\n",array);
The main difference is the missing &
that prefixes the array variable. Will it work? Compile and run to be sure.
Here’s my output for the new code:
'array' is at address 0028FF0C
'array' is at address 0028FF0C
Is the &
prefix necessary? Better find out:
Exercise 19-3: Summon the source code from Exercise 18-6 (from Chapter 18). Edit Lines 10 through 14 to remove the &
from the variable's name in the printf()
statement. Attempt to build the program.
Here’s the error message I saw repeated four times:
Warning: format '%p' expects type 'void *' ...
Obviously, the &
is important for individual variables. But for arrays, it's optional and, indeed, ignored. But how could that be, unless . . . unless an array is really a pointer!
Working pointer math in an array
What happens when you increment a pointer? Say that pointer variable dave
references a variable at memory address 0x8000. If so, consider this statement:
dave++;
What would the value of pointer dave
be?
Your first inclination might be to say that dave
would be incremented by 1, which is correct. But the result of the calculation may not be 0x8001. That's because the address stored in a pointer variable is incremented by one unit, not by one digit.
What’s a unit?
It depends on the variable type. If pointer dave
is a char
pointer, indeed the new address could be 0x8001. But if dave
were an int
or a float
, the new address would be the same as
0x8000 + sizeof(int)
or
0x8000 + sizeof(float)
On most systems, an int
is 4 bytes, so you could guess that dave
would equal 0x8004 after the increment operation. But why guess when you can code?
Listing 19-2 illustrates a simple program, something I could have directed you to code without using pointers: Fill an int
array with values 1
through 10
, and then display the array and its values. But in Listing 19-2, a pointer is used to fill the array.
Listing 19-2: Arrays and Pointer Math
#include <stdio.h>
int main()
{
int numbers[10];
int x;
int *pn;
pn = numbers; /* initialize pointer */
/* Fill array */
for(x=0;x<10;x++)
{
*pn=x+1;
pn++;
}
/* Display array */
for(x=0;x<10;x++)
printf("numbers[%d] = %d\n",
x+1,numbers[x]);
return(0);
}
Line 7 declares the pointer pn
, and Line 9 initializes it. The &
isn't needed here because numbers
is an array, not an individual variable. At that point, the pointer holds the base address of the array, as illustrated in Figure 19-1. Keep in mind that the array is empty.
Figure 19-1: Filling an array by using a pointer.
The for
loop at Lines 12 through 16 fills the numbers
array. The first element is filled at Line 14 using the peeker notation for pointer pn
. Then at Line 15, pointer pn
is incremented one unit. It now points at the next element in the array, as shown in Figure 19-1, and the loop repeats.
Exercise 19-4: Copy the source code from Listing 19-2 into your editor. Build and run.
Exercise 19-5: Modify your source code from Exercise 19-4 so that the address of each element in the array is displayed along with its value.
In the output of Exercise 19-5, you should see that each address is separated by 4 bytes (assuming that the size of an int
is 4 bytes on your machine). In fact, the addresses probably all end in the hex digits 0, 4, 8, and C.
Exercise 19-6: Complete the conversion of Listing 19-2, and what you began in Exercise 19-5, by having the second for
loop display the array's values using the peeker side of pointer variable pn
.
Exercise 19-7: Create a new project that fills a char
array by using pointers similar to the ones shown in Listing 19-2. Set the char
array's size to 27
so that it can hold 26 letters. Fill the array with the letters 'A'
through 'Z'
by using pointer notation. Display the results by using pointer notation.
Here’s a big hint:
*pn=x+'A';
In fact, in case you’re totally lost, I’ve put my solution for Exercise 19-7 in Listing 19-3.
Listing 19-3: My Solution to Exercise 19-7
#include <stdio.h>
int main()
{
char alphabet[27];
int x;
char *pa;
pa = alphabet; /* initialize pointer */
/* Fill array */
for(x=0;x<26;x++)
{
*pa=x+'A';
pa++;
}
pa = alphabet;
/* Display array */
for(x=0;x<26;x++)
{
putchar(*pa);
pa++;
}
putchar('\n');
return(0);
}
The source code in Listing 19-3 should be rather lucid, performing each task one step at a time. But keep in mind that many C programmers like to combine statements, and such combinations happen frequently with pointers.
Exercise 19-8: Combine the two statements in the first for
loop from Listing 19-3 to be only one statement:
*pa++=x+'A';
Ensure that you type it in properly. Build and run.
The output is the same. What that ugly mess does is described here:
|
This part of the statement is executed first, adding the value of variable |
|
The result of |
|
The value of variable |
Keeping the two statements separate still works, and I code all my programs that way because it’s easier for me to read later. But not every programmer does that! Many of them love to stack up pointers with the increment operator. Watch out for it! Or, if you understand it, use it.
Exercise 19-9: Fix up your source code from Exercise 19-8 so that the second for
loop uses the *pa++
monster.
Hopefully, the *pa++
pointer-thing makes sense. If not, take a nap and then come back and examine Listing 19-4.
Listing 19-4: Head-Imploding Program
#include <stdio.h>
int main()
{
char alpha = 'A';
int x;
char *pa;
pa = α /* initialize pointer */
for(x=0;x<26;x++)
putchar((*pa)++);
putchar('\n');
return(0);
}
The source code from Listing 19-4 deals with a single char
variable and not an array. Therefore, the pointer initialization in Line 9 requires the &
prefix. Don't forget that!
Line 12 in this code contains the booger (*pa)++
. It looks similar to *pa++
, but it's definitely not. Unlike *pa++
, which peeks at a value and then increments the pointer, the (*pa)++
construction increments a value being peeked at; the pointer is unchanged.
Exercise 19-10: Edit, build, and run a new program by using the source code from Listing 19-4.
The (*pa)++
operation works, thanks to the parentheses. The program fetches the value represented by *pa
first and then that value is incremented. The pointer variable, pn
, isn't affected by the operation.
To help avoid confusion on this topic, I offer Table 19-1, which explains the various cryptic pointer/peeker notation doodads.
Table 19-1 Pointers and Peekers In and Out of Parentheses
Expression |
Address p |
Value *p |
*p++ |
Incremented after the value is read |
Unchanged |
*(p++) |
Incremented after the value is read |
Unchanged |
(*p)++ |
Unchanged |
Incremented after it’s read |
*++p |
Incremented before the value is read |
Unchanged |
*(++p) |
Incremented before the value is read |
Unchanged |
++*p |
Unchanged |
Incremented before it’s read |
++(*p) |
Unchanged |
Incremented before it’s read |
Use Table 19-1 to help you decipher code as well as get the correct format for what you need done with a pointer. If the pointer notation you see or want doesn't appear in Table 19-1, it's either not possible or not a pointer. For example, the expressions p*++
and p++*
may look like they belong in Table 19-1, but they're not pointers. (In fact, they're not defined as valid expressions in C.)
Substituting pointers for array notation
Array notation is truly a myth because it can easily be replaced by pointer notation. In fact, internally to your programs, it probably is.
Consider Table 19-2, which compares array notation with pointer notation. Assume that pointer a
is initialized to array alpha
. The array and pointer must be the same variable type, but the notation doesn't differ between variable types. A char
array and an int
array would use the same references.
Table 19-2 Array Notation Replaced by Pointers
Array alpha[] |
Pointer a |
alpha[0] |
*a |
alpha[1] |
*(a+1) |
alpha[2] |
*(a+2) |
alpha[3] |
*(a+3) |
alpha[n] |
*(a+n) |
You can test your knowledge of array-to-pointer notation by using a sample program, such as the one shown in Listing 19-5.
Listing 19-5: A Simple Array Program
#include <stdio.h>
int main()
{
float temps[5] = { 58.7, 62.8, 65.0, 63.3, 63.2 };
printf("The temperature on Tuesday will be %.1f\n",
temps[1]);
printf("The temperature on Friday will be %.1f\n",
temps[4]);
return(0);
}
Exercise 19-11: Modify the two printf()
statements from Listing 19-5, replacing them with pointer notation.
Strings Are Pointer-Things
C lacks a string variable, but it does have the char
array, which is effectively the same thing. As an array, a string in C can be completely twisted, torqued, and abused by using pointers. It's a much more interesting topic than messing with numeric arrays, which is why it gets a whole section all by itself.
Using pointers to display a string
You're most likely familiar with displaying a string in C, probably by using either the puts()
or printf()
function. Strings, too, can be displayed one character a time by plodding through an array. To wit, I offer Listing 19-6.
Listing 19-6: Hello, String
#include <stdio.h>
int main()
{
char sample[] = "From whence cometh my help?\n";
int index = 0;
while(sample[index] != '\0')
{
putchar(sample[index]);
index++;
}
return(0);
}
The code shown in Listing 19-6 is completely legitimate C code, valid to create a program that displays a string. But it doesn’t use pointers, does it?
Exercise 19-12: Modify the source code from Listing 19-6, replacing array notation with pointer notation. Eliminate the index
variable. You need to create and initialize a pointer variable.
The while
loop's evaluation in Listing 19-6 is redundant. The null character evaluates as false. So the evaluation could be rewritten as
while(sample[index])
As long as the array element referenced by sample[index]
isn't a null character, the loop spins.
Exercise 19-13: Edit the while
loop's evaluation in your solution for Exercise 19-12, eliminating the redundant comparison.
Exercise 19-14: Continue working on your code, and this time eliminate the statements in the while
loop. Place all the action in the while
statement's evaluation. For the sake of reference, the putchar()
function returns the character that's displayed.
Declaring a string by using a pointer
Here’s a scary trick you can pull using pointers, one that comes with a boatload of caution. Consider Listing 19-7.
Listing 19-7: A Pointer Announces a String
#include <stdio.h>
int main()
{
char *sample = "From whence cometh my help?\n";
while(putchar(*sample++))
;
return(0);
}
In Listing 19-7, the string that’s displayed is created by initializing a pointer. It’s a construct that looks odd, but it’s something you witness often in C code, particularly with strings. (You cannot use this convention to initialize a numeric array.)
Exercise 19-15: Copy the source code from Listing 19-7 in your editor. Build and run.
The boatload of caution in Listing 19-7, and anytime you use a pointer to directly declare a string, is that the pointer variable can't be manipulated or else the string is lost. For example, in Listing 19-7, the sample
variable is used in Line 7 to step through the string as part of the putchar()
function. Oops. If I wanted to use sample
later in the code, it would no longer reference the start of the string.
The solution is to save the pointer’s initial address or simply use a second pointer to work on the string.
Exercise 19-16: Fix the code in Listing 19-7 so that the sample
variable's value is saved before the while
loop runs and is then restored afterward. Add a puts(sample)
statement to the code after the while
loop is done executing, to prove that the variable's original address is restored.
Building an array of pointers
An array of pointers would be an array that holds memory locations. Such a construction is often necessary in C, and I could devise a wickedly complex demo program that would frustrate you to insanity. But that doesn’t happen when you consider that an array of pointers is really an array of strings, shown in Listing 19-8. That makes topic digestion easier.
Listing 19-8: Crazy Pointer Arrays
#include <stdio.h>
int main()
{
char *fruit[] = {
"watermelon",
"banana",
"pear",
"apple",
"coconut",
"grape",
"blueberry"
};
int x;
for(x=0;x<7;x++)
puts(fruit[x]);
return(0);
}
An array of pointers is declared in Listing 19-8. It works similarly to Listing 12-7 (from Chapter 12), although in this construction you don’t need to specifically count individual string lengths. That’s because the array is really an array of pointers, or memory locations. Each string dwells somewhere in memory. The array simply lists where each one starts.
Exercise 19-17: Type the source code from Listing 19-8 into your editor. Build and run to confirm that it works.
This chapter covers pointers, so which part of Listing 19-8 do you think could be improved?
Exercise 19-18: Using information from Table 19-2 as your guide, replace the array notation at Line 17 in Listing 19-8 with pointer notation.
The reason that your solution to Exercise 19-18 works (assuming that you got it correct) is that the fruit
array contains pointers. The value of each element is another pointer. But that's nothing; consider Listing 19-9.
Listing 19-9: Pointers-to-Pointers Example
#include <stdio.h>
int main()
{
char *fruit[] = {
"watermelon",
"banana",
"pear",
"apple",
"coconut",
"grape",
"blueberry"
};
int x;
for(x=0;x<7;x++)
{
putchar(**(fruit+x));
putchar('\n');
}
return(0);
}
Line 18 in Listing 19-9 contains the dreaded, feared, avoided, and cursed **
notation, or double-pointer notation. To use my preferred nomenclature, it's a double-peeker. Before I commence the discussion, do Exercise 19-19.
Exercise 19-19: Carefully type the source code from Listing 19-9 into your editor. Compile and run.
To understand the **(fruit+x)
construct, you must work from the inside out:
fruit+x
Variable fruit
contains a memory address. It's a pointer! The x
is a value incrementing by one unit. In this case, the unit is an address because all elements of the fruit
array are pointers.
*(fruit+x)
You've seen the preceding construction already. It's the contents of the address fruit+x
. From the code, fruit
is an array of pointers. So the result of the preceding operation is . . . a pointer!
**(fruit+x)
Finally, you get a pointer to a pointer or — put better — a peeker to a peeker. If the inside peeker is a memory address, the outside peeker (the first asterisk) is the content of that memory address. Figure 19-2 attempts to clear up this concept.
Figure 19-2: How the **
thing works.
It helps to remember that the **
operator is almost always (but not exclusively) tied to an array of pointers; or, if you want to make it simple, to an array of strings. So in Figure 19-2, the first column is the address of an array of pointers, the second column is the pointer itself (a string), and the column on the right is the first character of the string.
If you're still confused — and I don't blame you; Einstein was in knots at this point when he read this book's first edition — consider mulling over Table 19-3. In the table, pointer notation (using variable ptr
) is compared with the equivalent array notation (using variable array
).
Table 19-3 Pointer Notation and Array Notation
Pointer Notation |
Array Notation |
Description |
**ptr |
*array[] |
Declares an array of pointers. |
*ptr |
array[0] |
The address of the first pointer in the array; for a string array, the first string. |
*(ptr+0) |
array[0] |
The same as the preceding entry. |
**ptr |
array[0][0] |
The first element of the first pointer in the array; the first character of the first string in the array. |
**(ptr+1) |
array[1][0] |
The first element of the second pointer in the array; the first character of the second string. |
*(*(ptr+1)) |
array[1][0] |
The same as the preceding entry. |
*(*(ptr+a)+b) |
array[a][b] |
Element |
**(ptr+a)+b |
array[a][0]+b |
This item isn't really what you want. What this item represents is the value of element 0 at pointer a plus the value of variable b. Use the |
Exercise 19-20: Rework your source code from Exercise 19-19 so that each individual character in a string is displayed, one at a time, by using the putchar()
function. If you can write the entire putchar()
operation as a while
loop's condition, you get ten bonus For Dummies points.
Sorting strings
Taking what you know about sorting in the C language (gleaned from Chapter 12), you can probably craft a decent string-sorting program. Or, at minimum, you can explain how it’s done. That’s great! But it’s a lot of work.
What’s better when it comes to sorting strings is not to sort the strings at all. No, instead, you sort an array of pointers referencing the strings. Listing 19-10 shows an example.
Listing 19-10: Sorting Strings, Initial Attempt
#include <stdio.h>
int main()
{
char *fruit[] = {
"apricot",
"banana",
"pineapple",
"apple",
"persimmon",
"pear",
"blueberry"
};
char *temp;
int a,b,x;
for(a=0;a<6;a++)
for(b=a+1;b<7;b++)
if(*(fruit+a) > *(fruit+b))
{
temp = *(fruit+a);
*(fruit+a) = *(fruit+b);
*(fruit+b) = temp;
}
for(x=0;x<7;x++)
puts(fruit[x]);
return(0);
}
Exercise 19-21: Type the source code from Listing 19-10 into your editor. Build and run to ensure that the strings are properly sorted.
Well, it probably didn’t work. It may have, but if the list is sorted or changed in any way, it’s an unintended consequence and definitely not repeatable.
The problem is in Line 19. You can't compare strings by using the >
operator. You can compare individual characters and you could then sort the list based on those characters, but most humans prefer words sorted across their entire length, not just the first character.
Exercise 19-22: Modify your source code, and use the strcmp()
function to compare strings to determine whether they need to be swapped.
Pointers in Functions
A pointer is a type of variable. As such, it can easily be flung off to a function. Even more thrilling, a pointer can wander back from a function as a return value. Oftentimes, these tricks are the only ways to get information to or from to a function.
Passing a pointer to a function
The great advantage of passing a pointer to a function is that the information that’s modified need not be returned. That’s because the function references a memory address, not a value directly. By using that address, information can be manipulated without being returned. Listing 19-11 demonstrates.
Listing 19-11: Pointing at a Discount
#include <stdio.h>
void discount(float *a);
int main()
{
float price = 42.99;
printf("The item costs $%.2f\n",price);
discount(&price);
printf("With the discount, that's $%.2f\n",price);
return(0);
}
void discount(float *a)
{
*a = *a * 0.90;
}
In Line 3 of Listing 19-11, the discount()
function is prototyped. It requires a float
type of pointer variable as its only argument.
Line 10 passes the address of the price
variable to the discount()
function. The percent sign obtains the memory location of the price
variable.
Within the function, pointer variable a
is used to peek at the value at the memory location that's passed.
Exercise 19-23: Type the source code from Listing 19-11 into your editor. Build and run the program.
Exercise 19-24: Modify your source code from Exercise 19-23 so that a float
pointer variable p
is declared in the main()
function. Initialize p
to the price
variable's location, and then pass p
to the discount()
function.
Exercise 19-25: Build a new project with two functions: create()
and show()
. The create()
function receives a pointer to an array of ten integers and fills that array with random values in the range of 0
through 9
. The show()
function receives the same array and displays all ten elements.
Returning a pointer from a function
Functions are known by their types, such as int
or char
or even void
. You can also declare pointer functions, which return a memory location as a value. Simply declare the function as being of a pointer type, such as
char *monster(void)
In this example, the monster()
function is declared. It requires no arguments but returns a pointer to a char
array — a string value.
Listing 19-12: Reversing a String
#include <stdio.h>
char *strrev(char *input);
int main()
{
char string[64];
printf("Type some text: ");
fgets(string,62,stdin);
puts(strrev(string));
return(0);
}
char *strrev(char *input)
{
static char output[64];
char *i,*o;
i=input; o=output;
while(*i++ != '\n')
;
i--;
while(i >= input)
*o++ = *i--;
*o = '\0';
return(output);
}
Listing 19-12 can get quite confusing. Pay attention!
Line 3 prototypes the strrev()
function. It returns a pointer — in this case, the address of a char
array or string.
The main()
function at Line 5 is pretty easy to figure out. Input is gathered by the fgets()
function at Line 10. It's passed to strrev()
at Line 11 inside the puts()
function.
The strrev()
function begins at Line 16. It requires a char
pointer as its argument, which is referred to as input
in the function. The output
buffer is created at Line 18, and it's static
, so it doesn't go away when the function is done. Line 19 declares two char
pointers: i
and o
.
The first while
loop at Line 23 finds the newline character at the end of the input string. The i
variable marches through the string one character at a time.
After finding the newline, the i
pointer contains the address of the next character in input
, which is probably not what you want. So the statement at Line 25 backs up i
to point at the last character in the string before the newline.
At the start of the while
loop at Line 27, pointer o
holds the base of the output
buffer, the first character, and pointer i
holds the last. Try to think of this situation as i
standing at the top of a staircase and o
standing at the bottom.
The while
loop spins until the address in pointer i
matches the address at the start of the input
string. As i
is decremented, the characters at address i
are copied to address o
. Figuratively, i
marches down the stairs, and o
marches up.
The return
statement at Line 31 sends the address of the output
buffer, the reversed string, back to the calling statement.
Exercise 19-26: Type the source code from Listing 19-12 into your editor. As you type the code, add your own comments describing what’s going on. Feel free to use my text as a guide. Build and run the program.