![]() |
Essentials of C++: Section X |
Pointers and Dynamic Memory Pointers are memory locations that hold the addresses of other memory
locations. They allow one to have direct access to memory locations
holding either data of some type or a function definition. The focus here
is on data pointers. A pointer must point to a value of a specific type or
to the type void - a pointer to anything. To declare a pointer one
uses the syntax:
as in: This declares the variable 'ptr' as a pointer to a memory location that
holds the address of a double. The '*' can be 'attached' to the type name,
between the type and identifier, or 'attached' to the identifier. It is
recommended, however, that it be 'attached' to the type name. Although it
is possible to declare many variables of the same type as in: it is not as simple to do the same with pointers. The line should be read as a declaration of one variable of type "pointer
to a double" and two variables of simply type double. B. Pointer Assignment There are four ways to assign an address to a pointer variable:
The 'new' operator is used to allocate memory from
the heap, that is, the memory not already used by the operating
system or the program. This process is referred to as dynamic memory
allocation because the amount of memory allocated to a program may
change as the program is executing. The general form for memory allocation using 'new' is: as in: The 'new' operator checks to see if there is enough memory available of the type requested. If so, it marks that memory as used and returns its address so that it can be assigned to a pointer variable, such as 'chPtr' in the example above. If not enough memory is available, 'new' returns NULL. (Handling this problem cleanly is a task beyond the scope of this discussion. There are three options. Look in your C++ manual for a discussion of the 'assert' statement, new_handler, or, for the most sophisticated approach, exception handling.) Note that in this example 'chPtr' is declared as a "pointer to a char".
This allows it to hold the address of a char - which is what the 'new'
operator in the second line returns. To allocate enough memory for an array one adds the size of the array
after the type name as in: Note that 'string' points to a character - which the second line tells
us will actually be the first character of an array of 21 characters. The
memory set aside is uninitialized. When the second statement here is
complete, 'string' holds an address BUT the memory location corresponding
to that address contains 'garbage'. C. De-Referencing Pointers as is: In this case '*chPtr' refers to the memory pointed to by the variable
'chPtr'. What will be output is the value in the memory location pointed
to by 'chPtr', NOT the address held in 'chPtr'. The result is
unpredictable if the variable being de-referenced contains NULL.
In general, programmers do not know the addresses represented by the identifiers used to name variables, constants, and functions in their programs. In C++, however, it is possible to get access to those addresses using the reference or address operator, '&'. Used in the form: the '&' operator returns the address of the memory whose symbolic
name is some-variable. For example, in the code: 'var2' will hold the address of 'var1' after the '&' operator is
finished. This same '&' symbol is used in the declaration of reference
types. The form for these is: For example: In this example, 'var4' will hold the address of (will point to)
'var3'. Notice the form of the declaration. A reference type must be
defined at the moment of declaration. One cannot simply write: Also notice that the address of 'var3' is retrieved automatically. There is nothing in the code that explicitly asks for the address of 'var3' as there is in the code: This is the key difference between reference types and pointer types.
Reference variables also hold addresses, but, unlike pointers, one does
not use the '*' or '&' operators in conjunction with them because
reference variables are de-referenced automatically. For example, in the
code: the system does not treat '10' as an address to store in 'var4'. (This
is illegal with pointers.) Instead, this will result in the variable
'var3' having the value 10 because 'var4' points to 'var3' and reference
variables are always de-referenced automatically - the address is followed
to its destination. However, when the '&' operator is used as in the
example above involving 'var2', it is legal to reference var1 by
writing: Note that the '&' symbol has three different uses in C++ that are
all related to addressing. (We ignore the bitwise logical '&'
operator.) It can be used to:
In its third capacity it acts very much as a reference type in that in
all cases, the parameter is automatically referenced. For that reason,
reference variables and reference parameters are often considered aliases
of the identifier whose memory they reference. E. Arrays and Pointers Upon completion of these three lines, 'intPtr' holds the address of
'intArray'. In other words, it holds the address of the first integer in
this array of integers. A variation of this is used in parameter
declarations. Formal parameters are often declared as pointers to some
type while the actual parameter is an array of the same type.
F. Pointer Arithmetic When adding or subtracting a pointer and an integer, it is assumed that
the pointer refers to an array. Adding or subtracting a value to (or from)
a pointer causes the address held by the pointer to change by "n times the
number of bytes required for the type pointed to". For example, if
'intPtr' in the example above is used in the following expression: the address pointed to by 'intPtr' is incremented by 10 - if we assume
that integers require 2 bytes. If integers required 4 bytes, the address
would be incremented by 20. In the case of subtraction, the address would
be decremented by the appropriate amount. One often sees code such as: or This shifts the pointer to the point to the next (or previous) element
of the implied array and is equivalent to: or One can also subtract one pointer from another although this only has
meaning if the two pointers point to the same array. Such expressions
return an integer representing the number of array elements separating the
two pointers. Consider the following code: The variable 'x' should have the value 6. All these operations assume that the programmer is being careful to
stay within the bounds of the array being pointed to. C++ does not check
to see if one has shifted past the end or before the beginning of the
array. Attempting to de-reference a pointer that no longer is within the
bounds of an array leads to undefined and potentially dangerous
behavior. Finally, one can also compare pointers using the equality, less than
and greater than operators. G. Delete and The first form is used with non-array variables as in: The memory location represented by 'chPtr' still exists BUT the memory
that 'chPtr' pointed to has been returned to the heap. With arrays the situation is a bit more complex. Historically, C++ has used three different methods for deleting arrays because of the problems involving class instances containing user-defined destructors. In the latest approach (the second form above) one simply uses the brackets ([]) to indicate that the system needs to consider the possibility that the memory to be deleted includes class instances with destructors. The system itself then handles the problems. Earlier approaches required (in all or some circumstances) that the number of elements in the array be included in the brackets. Links to 'The Story of C++" and other documents
|