|

To
declare a pointer, write the type of the variable
or object whose address will be stored in the pointer,
followed by the pointer operator (*) and the name
of the pointer. For example,
unsigned
short int * pPointer = 0;
To
assign or initialize a pointer, prepend the name
of the variable whose address is being assigned
with the address of operator (&). For example,
unsigned
short int theVariable = 5;
unsigned
short int * pPointer = & theVariable;
To
dereference a pointer, prepend the pointer name
with the dereference operator (*). For example,
unsigned
short int theValue = *pPointer
Why
Would You Use Pointers?
So
far you’ve seen step-by-step details of assigning
a variable’s address to a pointer. In practice,
though, you would never do this. After all, why
bother with a pointer when you already have a variable
with access to that value? The only reason for this
kind of pointer manipulation of an automatic variable
is to demonstrate how pointers work. Now that you
are comfortable with the syntax of pointers, you
can put them to good use. Pointers are used, most
often, for three tasks:
·
Managing data on the free store.
·
Accessing class member data and functions.
·
Passing variables by reference to functions.
This
rest of this unit focuses on managing data on the
free store and accessing class member data and functions.
Tomorrow you will learn about passing variables
by reference.
The
Stack and the Free Store
In
the "Extra Credit" section following the discussion
of functions in unit 5, five areas of memory are
mentioned:
·
Global name space
·
The free store
·
Registers
·
Code space
·
The stack
Local
variables are on the stack, along with function
parameters. Code is in code space, of course, and
global variables are in global name space. The registers
are used for internal housekeeping functions, such
as keeping track of the top of the stack and the
instruction pointer. Just about all remaining memory
is given over to the free store, which is sometimes
referred to as the heap.
The
problem with local variables is that they don’t
persist: When the function returns, the local variables
are thrown away. Global variables solve that problem
at the cost of unrestricted access throughout the
program, which leads to the creation of code that
is difficult to understand and maintain. Putting
data in the free store solves both of these problems.
You
can think of the free store as a massive section
of memory in which thousands of sequentially numbered
cubbyholes lie waiting for your data. You can’t
label these cubbyholes, though, as you can with
the stack. You must ask for the address of the cubbyhole
that you reserve and then stash that address away
in a pointer.
One
way to think about this is with an analogy: A friend
gives you the 800 number for Acme Mail Order. You
go home and program your telephone with that number,
and then you throw away the piece of paper with
the number on it. If you push the button, a telephone
rings somewhere, and Acme Mail Order answers. You
don’t remember the number, and you don’t know where
the other telephone is located, but the button gives
you access to Acme Mail Order. Acme Mail Order is
your data on the free store. You don’t know where
it is, but you know how to get to it. You access
it by using its address—in this case, the telephone
number. You don’t have to know that number; you
just have to put it into a pointer (the button).
The pointer gives you access to your data without
bothering you with the details.
The
stack is cleaned automatically when a function returns.
All the local variables go out of scope, and they
are removed from the stack. The free store is not
cleaned until your program ends, and it is your
responsibility to free any memory that you’ve reserved
when you are done with it.
The
advantage to the free store is that the memory you
reserve remains available until you explicitly free
it. If you reserve memory on the free store while
in a function, the memory is still available when
the function returns.
The
advantage of accessing memory in this way, rather
than using global variables, is that only functions
with access to the pointer have access to the data.
This provides a tightly controlled interface to
that data, and it eliminates the problem of one
function changing that data in unexpected and unanticipated
ways.
For
this to work, you must be able to create a pointer
to an area on the free store and to pass that pointer
among functions. The following sections describe
how to do this.
new
You
allocate memory on the free store in C++ by using
the new keyword. new is followed by the type of
the object that you want to allocate so that the
compiler knows how much memory is required. Therefore,
new unsigned short int allocates two bytes in the
free store, and new long allocates four.
The
return value from new is a memory address. It must
be assigned to a pointer. To create an unsigned
short on the free store, you might write
unsigned
short int * pPointer;
pPointer
= new unsigned short int;
You
can, of course, initialize the pointer at its creation
with
unsigned
short int * pPointer = new unsigned short int;
In
either case, pPointer now points to an unsigned
short int on the free store. You can use this like
any other pointer to a variable and assign a value
into that area of memory by writing
*pPointer
= 72;
This
means, "Put 72 at the value in pPointer," or "Assign
the value 72 to the area on the free store to which
pPointer points."
If
new cannot create memory on the free store (memory
is, after all, a limited resource) it returns the
null pointer. You must check your pointer for null
each time you request new memory.
WARNING: Each time you allocate memory
using the new keyword, you must check to make sure
the pointer is not null.
delete
When
you are finished with your area of memory, you must
call delete on the pointer. delete returns the memory
to the free store. Remember that the pointer itself—as
opposed to the memory to which it points—is a local
variable. When the function in which it is declared
returns, that pointer goes out of scope and is lost.
The memory allocated with new is not freed automatically,
however. That memory becomes unavailable—a situation
called a memory leak. It’s called a memory leak
because that memory can’t be recovered until the
program ends. It is as though the memory has leaked
out of your computer.
To
restore the memory to the free store, you use the
keyword delete. For example,
delete
pPointer;
When
you delete the pointer, what you are really doing
is freeing up the memory whose address is stored
in the pointer. You are saying, "Return to the free
store the memory that this pointer points to." The
pointer is still a pointer, and it can be reassigned.
Listing 8.4 demonstrates allocating a variable on
the heap, using that variable, and deleting it.
WARNING: When you call delete on a
pointer, the memory it points to is freed. Calling
delete on that pointer again will crash your program!
When you delete a pointer, set it to zero (null).
Calling delete on a null pointer is guaranteed to
be safe. For example:
Animal
*pDog = new Animal; delete pDog; //frees the memory
pDog
= 0; //sets pointer to null //... delete pDog; //harmless
Listing
8.4. Allocating, using, and deleting pointers.
1:
// Listing 8.4
2:
// Allocating and deleting a pointer
3:
4:
#include <iostream.h>
5:
int main()
6:
{
7:
int localVariable = 5;
8:
int * pLocal= &localVariable;
9:
int * pHeap = new int;
10:
if (pHeap == NULL)
11:
{
12:
cout << "Error! No memory for pHeap!!";
13:
return 0;
14:
}
15:
*pHeap = 7;
16:
cout << "localVariable: " << localVariable
<< "\n";
17:
cout << "*pLocal: " << *pLocal <<
"\n";
18:
cout << "*pHeap: " << *pHeap <<
"\n";
19:
delete pHeap;
20:
pHeap = new int;
21:
if (pHeap == NULL)
22:
{
23:
cout << "Error! No memory for pHeap!!";
24:
return 0;
25:
}
26:
*pHeap = 9;
27:
cout << "*pHeap: " << *pHeap <<
"\n";
28:
delete pHeap;
29:
return 0;
30:
}
Output:
localVariable: 5
*pLocal: 5
*pHeap: 7
*pHeap: 9
Analysis: Line 7 declares and initializes
a local variable. Line 8 declares and initializes
a pointer with the address of the local variable.
Line 9 declares another pointer but initializes
it with the result obtained from calling new int.
This allocates space on the free store for an int.
Line 10 verifies that memory was allocated and the
pointer is valid (not null). If no memory can be
allocated, the pointer is null and an error message
is printed. To keep things simple, this error checking
often won’t be reproduced in future programs, but
you must include some sort of error checking in
your own programs.
Line
15 assigns the value 7 to the newly allocated memory.
Line 16 prints the value of the local variable,
and line 17 prints the value pointed to by pLocal.
As expected, these are the same. Line 19 prints
the value pointed to by pHeap. It shows that the
value assigned in line 15 is, in fact, accessible.
In
line 19, the memory allocated in line 9 is returned
to the free store by a call to delete. This frees
the memory and disassociates the pointer from that
memory. pHeap is now free to point to other memory.
It is reassigned in lines 20 and 26, and line 27
prints the result. Line 28 restores that memory
to the free store.
Although
line 28 is redundant (the end of the program would
have returned that memory) it is a good idea to
free this memory explicitly. If the program changes
or is extended, it will be beneficial that this
step was already taken care of.
|