The choice of a point of view is the initial act of culture.
There are things and pointers to things. Knowing the difference between the two is very important. This concept is illustrated in Figure 13-1.
In this book, we use a box to represent a thing. The name of the
variable is written on the bottom of the box. In this case, our variable
is named thing
. The value of the
variable is 6.
The address of thing
is
0x1000
. Addresses are automatically
assigned by the C compiler to every variable. Normally, you don’t have
to worry about the addresses of variables, but you should understand
that they’re there.
Our pointer (thing_ptr
) points
to the variable thing
. Pointers are
also called address variables because they contain
the addresses of other variables. In this case, our pointer contains the
address 0x1000
. Because this is the
address of thing
, we say that
thing_ptr
points to thing
.
Variables and pointers are much like street addresses and houses. For example, your address might be “214 Green Hill Lane.” Houses come in many different shapes and sizes. Addresses are approximately the same size (street, city, state, and zip). So, while “1600 Pennsylvania Ave.” might point to a very big white house and “8347 Undersea Street” might be a one-room shack, both addresses are the same size.
The same is true in C. While things may be big and small, pointers come in one size (relatively small).[15]
Many novice programmers get pointers and their contents confused.
In order to limit this problem, all pointer variables in this book end
with the extension _ptr
. You might
want to follow this convention in your own programs. Although this
notation is not as common as it should be, it is extremely
useful.
Many different address variables can point to the same thing. This concept is true for street addresses as well. Table 13-1 lists the location of important services in a small town.
Service (variable name) | Address (address value) | Building (thing) |
Fire Department | 1 Main Street | City Hall |
Police Station | 1 Main Street | City Hall |
Planning office | 1 Main Street | City Hall |
Gas Station | 2 Main Street | Ed’s Gas Station |
In this case, we have a government building that serves many functions. Although it has one address, three different pointers point to it.
As we will see in this chapter, pointers can be used as a quick and simple way to access arrays. In later chapters, we will discover how pointers can be used to create new variables and complex data structures such as linked lists and trees. As you go through the rest of the book, you will be able to understand these data structures as well as create your own.
A pointer is declared by putting an asterisk (*
) in front of the variable name in the
declaration statement:
int thing; /* define a thing */ int *thing_ptr; /* define a pointer to a thing */
Table 13-2 lists the operators used in conjunction with pointers.
The operator ampersand (&
)
returns the address of a thing which is a pointer. The operator asterisk
(*
) returns the object to which a
pointer points. These operators can easily cause confusion. Table 13-3 shows the syntax for the
various pointer operators.
C Code | Description |
| Simple thing (variable) |
| Pointer to variable |
| Pointer to an integer (may or may not be specific
integer |
| Integer |
Let’s look at some typical uses of the various pointer operators:
int thing; /* Declare an integer (a thing) */ thing = 4;
The variable thing
is a thing.
The declaration int thing
does
not contain an *
, so thing
is not a pointer:
int *thing_ptr; /* Declare a pointer to a thing */
The variable thing_ptr
is a
pointer. The *
in the declaration
indicates this is a pointer. Also, we have put the extension _ptr
onto the name:
thing_ptr = &thing; /* Point to the thing */
The expression &thing
is a
pointer to a thing. The variable thing
is an object. The &
(address of operator) gets the address
of an object (a pointer), so &thing
is a pointer. We then assign this
to thing_ptr
, also of type
pointer:
*thing_ptr = 5; /* Set "thing" to 5 */ /* We may or may not be pointing */ /* to the specific integer "thing" */
The expression *thing_ptr
indicates a thing. The variable thing_ptr
is a pointer. The *
(dereference operator) tells C to look at
the data pointed to, not the pointer itself. Note that this points to
any integer. It may or may not point to the specific variable thing
.
These pointer operations are summarized in Figure 13-2.
The following examples show how to misuse the pointer operations:
*thing
is illegal. It asks C to get the object pointed to by the
variable thing
. Because
thing
is not a pointer, this
operation is invalid.
&thing_ptr
is legal, but strange. thing_ptr
is a pointer. The &
(address of operator) gets a
pointer to the object (in this case thing_ptr
). The result is a pointer to a
pointer.
Example 13-1 illustrates a
simple use of pointers. It declares one object, one thing
, and a pointer, thing_ptr
. thing
is set explicitly by the line:
thing = 2;
The line:
thing_ptr = &thing;
causes C to set thing_ptr
to
the address of thing
. From this point
on, thing
and *thing_ptr
are the same.
#include <stdio.h> int main() { int thing_var; /* define a variable for thing */ int *thing_ptr; /* define a pointer to thing */ thing_var = 2; /* assigning a value to thing */ printf("Thing %d ", thing_var); thing_ptr = &thing_var; /* make the pointer point to thing */ *thing_ptr = 3; /* thing_ptr points to thing_var so */ /* thing_var changes to 3 */ printf("Thing %d ", thing_var); /* another way of doing the printf */ printf("Thing %d ", *thing_ptr); return (0); }
Several pointers can point to the same thing:
1: int something; 2: 3: int *first_ptr; /* one pointer */ 4: int *second_ptr; /* another pointer */ 5: 6: something = 1; /* give the thing a value */ 7: 8: first_ptr = &something; 9: second_ptr = first_ptr;
In line 8, we use the &
operator to change something
, a
thing, into a pointer that can be assigned to first_ptr
. Because first_ptr
and second_ptr
are both pointers, we can do a
direct assignment in line 9.
After executing this program fragment, we have the situation shown in Figure 13-3.
You should note that while we have three variables, there is only
one integer (something
). The
following are all equivalent:
something = 1; *first_ptr = 1; *second_ptr = 1;
C passes parameters using “call by value.” That is, the parameters go only one way into the function. The only result of a function is a single return value. This concept is illustrated in Figure 13-4.
However, pointers can be used to get around this restriction.
Imagine that there are two people, Sam and Joe, and whenever they meet, Sam can only talk and Joe can only listen. How is Sam ever going to get any information from Joe? Simple: all Sam has to do is tell Joe, “I want you to leave the answer in the mailbox at 335 West 5th Street.”
C uses a similar trick to pass information from a function to
its caller. In Example 13-2,
main
wants the function inc_count
to increment the variable count
.
Passing it directly would not work, so a pointer is passed
instead (“Here’s the address of the variable I want you to
increment”). Note that the prototype for inc_count
contains an int *
. This format indicates that the single
parameter given to this function is a pointer to an integer, not the
integer itself.
#include <stdio.h> void inc_count(int *count_ptr) { (*count_ptr)++; } int main() { int count = 0; /* number of times through */ while (count < 10) inc_count(&count); return (0); }
This code is represented graphically in Figure 13-5. Note that the parameter is not changed, but what it points to is changed.
Finally, there is a special pointer called NULL
. It points to nothing. (The actual
numeric value is 0.) The standard include file,
locale.h, defines the constant NULL
. (This file is usually not directly
included, but is usually brought in by the include files
stdio.h or stdlib.h.) The
NULL
pointer is represented
graphically in Figure
13-6.
Declaring constant pointers is a little tricky. For example, the declaration:
const int result = 5;
tells C that result
is a
constant so that:
result = 10; /* Illegal */
is illegal. The declaration:
const char *answer_ptr = "Forty-Two";
does not tell C that the variable answer_ptr
is a constant. Instead, it tells
C that the data pointed to by answer_ptr
is a constant. The data cannot be
changed, but the pointer can. Again we need to make sure we know the
difference between “things” and “pointers to things.”
What’s answer_ptr
? A pointer.
Can it be changed? Yes, it’s just a pointer. What does it point to? A
const char
array. Can the data
pointed to by answer_ptr
be
changed? No, it’s constant.
In C this is:
answer_ptr = "Fifty-One"; /* Legal (answer_ptr is a variable) */ *answer_ptr = 'X'; /* Illegal (*answer_ptr is a constant) */
If we put the const after the * we tell C that the pointer is constant.
For example:
char *const name_ptr = "Test";
What’s name_ptr
? It is a
constant pointer. Can it be changed? No. What does it point to? A
character. Can the data we pointed to by name_ptr
be changed? Yes.
name_ptr = "New"; /* Illegal (name_ptr is constant) */ *name_ptr = 'B'; /* Legal (*name_ptr is a char) */
Finally, we can put const in both places, creating a pointer that cannot be changed to a data item that cannot be changed:
const char *const title_ptr = "Title";
C allows pointer arithmetic (addition and subtraction). Suppose we have:
char array[5]; char *array_ptr = &array[0];
In this example, *array_ptr
is the same as array[0]
, *(array_ptr+1)
is the same as array[1]
, *(array_ptr+2)
is the same as array[2]
, and so on. Note the use of
parentheses. Pointer arithmetic is represented graphically in Figure 13-7.
However, (*array_ptr)+1
is
not the same as array[1]
. The +1
is outside the parentheses, so it is
added after the dereference. So (*array_ptr)+1
is the same as array[0]+1
.
At first glance, this method may seem like a complex way of representing simple array indices. We are starting with simple pointer arithmetic. In later chapters we will use more complex pointers to handle more difficult functions efficiently.
The elements of an array are assigned to consecutive addresses.
For example, array[0]
may be placed
at address 0xff000024
. Then
array[1]
would be placed at address
0xff000025
, and so on. This
structure means that a pointer can be used to find each element of the
array. Example 13-3 prints out
the elements and addresses of a simple character array.
#include <stdio.h> #define ARRAY_SIZE 10 char array[ARRAY_SIZE + 1] = "0123456789"; int main() { int index ; printf("&array[index] (array+index) array[index] "); for(index=0;index < ARRAY_SIZE;++index) printf("0x%-10p 0x%-10p 0x%x ",, &array[index],(array+index),array[index]); return 0; }
When run, this program prints:
&array[index] (array+index) array[index] 0x20a50 0x20a50 0x30 0x20a51 0x20a51 0x31 0x20a52 0x20a52 0x32 0x20a53 0x20a53 0x33 0x20a54 0x20a54 0x34 0x20a55 0x20a55 0x35 0x20a56 0x20a56 0x36 0x20a57 0x20a57 0x37 0x20a58 0x20a58 0x38 0x20a59 0x20a59 0x39
Characters use one byte, so the elements in a character array
will be assigned consecutive addresses. A short int
font uses two bytes, so in an
array of short int
, the addresses
increase by two. Does this mean that array+1
will not work for anything other
than characters? No. C automatically scales pointer arithmetic so that
it works correctly. In this case, array+1
will point to element number
1.
C provides a shorthand for dealing with arrays. Rather than writing:
array_ptr = &array[0];
we can write:
array_ptr = array;
C blurs the distinction between pointers and arrays by treating
them in the same manner in many cases. Here we use the variable
array
as a pointer, and C
automatically does the necessary conversion.
Example 13-4 counts the number of elements that are nonzero and stops when a zero is found. No limit check is provided, so there must be at least one zero in the array.
#include <stdio.h> int array[] = {4, 5, 8, 9, 8, 1, 0, 1, 9, 3}; int index; int main() { index = 0; while (array[index] != 0) ++index; printf("Number of elements before zero %d ", index); return (0); }
Example 13-5 is a version of Example 13-4 that uses pointers.
#include <stdio.h> int array[] = {4, 5, 8, 9, 8, 1, 0, 1, 9, 3}; int *array_ptr; int main() { array_ptr = array; while ((*array_ptr) != 0) ++array_ptr; printf("Number of elements before zero %d ", array_ptr - array); return (0); }
Notice that when we wish to examine the data in the array, we
use the dereference operator (*
).
This operator is used in the statement:
while ((*array_ptr) != 0)
When we wish to change the pointer itself, no other operator is used. For example, the line:
++array_ptr;
increments the pointer, not the data.
Example 13-4 uses the
expression (array[index] != 0)
.
This expression requires the compiler to generate an index operation,
which takes longer than a simple pointer dereference, ((*array_ptr) != 0)
.
The expression at the end of this program, array_ptr - array
, computes how far array_ptr
is into the array.
When passing an array to a procedure, C will automatically
change the array into a pointer. In fact, if you put &
before the array, C will issue a
warning. Example 13-6
illustrates the various ways in which an array can be passed to a
subroutine.
#define MAX 10 /* Size of the array */ /******************************************************** * init_array_1 -- Zeroes out an array. * * * * Parameters * * data -- The array to zero out. * ********************************************************/ void init_array_1(int data[]) { int index; for (index = 0; index < MAX; ++index) data[index] = 0; } /******************************************************** * init_array_2 -- Zeroes out an array. * * * * Parameters * * data_ptr -- Pointer to array to zero. * ********************************************************/ void init_array_2(int *data_ptr) { int index; for (index = 0; index < MAX; ++index) *(data_ptr + index) = 0; } int main() { int array[MAX]; void init_array_1(); void init_array_2(); /* one way of initializing the array */ init_array_1(array); /* another way of initializing the array */ init_array_1(&array[0]); /* works, but the compiler generates a warning */ init_array_1(&array); /* Similar to the first method but */ /* function is different */ init_array_2(array); return (0); }
The major goal of this book is to teach you how to create clear, readable, maintainable code. Unfortunately, not everyone has read this book and some people still believe that you should make your code as compact as possible. This belief can result in programmers using the ++ and — operators inside other statements.
Example 13-7 shows several examples in which pointers and the increment operator are used together.
/* This program shows programming practices that should **NOT** be used */ /* Unfortunately, too many programmers use them */ int array[10]; /* An array for our data */ int main() { int *data_ptr; /* Pointer to the data */ int value; /* A data value */ data_ptr = &array[0];/* Point to the first element */ value = *data_ptr++; /* Get element #0, data_ptr points to element #1 */ value = *++data_ptr; /* Get element #2, data_ptr points to element #2 */ value = ++*data_ptr; /* Increment element #2, return its value */ /* Leave data_ptr alone */
To understand each of these statements, you must carefully dissect each expression to discover its hidden meaning. When I do maintenance programming, I don’t want to have to worry about hidden meanings, so please don’t code like this, and shoot anyone who does.
These statements are dissected in Figure 13-8.
This example is a little extreme, but it illustrates how side effects can easily become confusing.
Example 13-8 is an
example of the code you’re more likely to run into. The program copies
a string from the source (q
) to the
destination (p
).
Given time, a good programmer will decode this. However, understanding the program is much easier when we are a bit more verbose, as in Example 13-9.
/******************************************************** * copy_string -- Copies one string to another. * * * * Parameters * * dest -- Where to put the string * * source -- Where to get it * ********************************************************/ void copy_string(char *dest, char *source) { while (1) { *dest = *source; /* Exit if we copied the end of string */ if (*dest == ' ') return; ++dest; ++source; } }
Suppose we are given a string of the form “Last/First.” We want to split this into two strings, one containing the first name and one containing the last name.
We need a function to find the slash in the name. The standard
function strchr
performs this job
for us. In this program, we have chosen to duplicate this function to
show you how it works.
This function takes a pointer to a string (string_ptr
) and a character to find
(find
) as its arguments. It starts
with a while loop that will continue
until we find the character we are looking for (or we are stopped by
some other code below).
while (*string_ptr != find) {
Next we test to see if we’ve run out of string. In this case,
our pointer (string_ptr
) points to
the end-of-string character. If we have reached the end of string
before finding the character, we return NULL
:
if (*string_ptr == ' ') return (NULL);
If we get this far, we have not found what we are looking for, and are not at the end of the string. So we move the pointer to the next character, and return to the top of the loop to try again:
++string_ptr; }
Our main program reads in a single line, stripping the newline
character from it. The function my_strchr
is called to find the location of
the slash (/).
At this point, last_ptr
points to the first character of the last name and first_ptr
points to slash. We then split the
string by replacing the slash (/) with an end of string (NUL or