Essentialssec10.htm

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:

type-name * identifier-name;

as in:

double* ptr;

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:

double var1, var2, var3;

it is not as simple to do the same with pointers. The line

double* var1, var2, var3;     // This does not mean what it seems to mean!!!

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
Before using a pointer, it needs to be initialized. When declared, a pointer holds some random memory address. Accessing that address could result in strange behavior by the program.

There are four ways to assign an address to a pointer variable:

  1. By assigning a pointer the null pointer value, 0. This signifies that the pointer points to nothing. The identifier NULL, defined in the standard header file, stdio.h, can be used instead of 0 for readability.
  2. By assigning the value of one pointer to another as in:

    int* ptr1 = 0;
    int* ptr2;
    ptr2 = ptr1;

  3. By using the reference operator, '&'.
  4. By using the new operator.

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:

some-variable = new a-type-name-that-matches-the-type-of-the-variable;

as in:

char* chPtr;
chPtr = new char;

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:

char* string;
string = new char[21];

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
To access the memory pointed to by a pointer, one must "follow the pointer" or de-reference the pointer. This is also referred to as indirect addressing and the operator that performs this operation is called the indirection operator. It has the form:

*some-variable

as is:

cout << *chPtr;

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.
 

D. The Reference Operator

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:

&some-variable

the '&' operator returns the address of the memory whose symbolic name is some-variable. For example, in the code:

int var1;
int* var2 = &var1;

'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:

some-type-name& some-variable-name = some-variable-of-same-type;

For example:

double var3 = 2.2;
double& var4 = var3;

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:

double& var4; // This is illegal

Also notice that the address of 'var3' is retrieved automatically. There is nothing in the code

double& var4 = var3;

that explicitly asks for the address of 'var3' as there is in the code:

int* var2 = &var1;

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:

var4 = 10;

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:

*var2

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:

  1. return the address of an identifier acting as a function name or lvalue;
  2. declare a reference variable as a reference type;
  3. declare a formal parameter as a reference parameter.

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
In C++ arrays and pointers are closely related. Array names can be seen as pointers to the first element of the array they reference. For that reason, it is redundant to declare array parameters as reference parameters or to use the '&' operator with arrays. To have a pointer point to an array one simply needs to use the assignment statement as in the following:

int intArray[20];
int* intPtr;
intPtr = intArray;

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
Pointers can be involved in expressions involving addition, subtraction, and comparisons. (This refers to pointers themselves without the de-referencing operator. When used with the de-referencing operator, pointers can be used anywhere the type being pointed to can be used.)

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:

intPtr += 5

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:

intPtr++;

or

intPtr--;

This shifts the pointer to the point to the next (or previous) element of the implied array and is equivalent to:

intArray[i++];

or

intArray[i--];

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:

double dblArray[10];
double* dblPtr1 = dblArray;
double* dblPtr2 = dblPtr1;
dblPtr2 += 6;
int x = dblPtr2 - dblPtr1;

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
To return memory to the heap, one uses the delete operator. It has two forms:

delete some-variable;
and
delete [] some-variable;

The first form is used with non-array variables as in:

char* chPtr;
ch = new chPtr;
delete chPtr;

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

Chapter on Pointers
Arrays and Pointers
Pointer Arithmetic
The Use of '&' to Declare Reference Parameters
The Delete Operator

Top of Section Main Menu of Essentials of C++ Next Section