What`s the Address? Pointers

The name “pointer” describes the job of the item; a pointer “points” to another variable or constant. Some tasks in C++ are easier to do with pointers; others would be utterly impossible without pointers. This article discusses how to create and work with pointers in C++. It is taken from chapter 11 of the book C++ Demystified, written by Jeff Kent (McGraw-Hill/Osborne, 2004; ISBN: 0072253703).

What’s the Address? Pointers

My parents told me when I was a child that it was not polite to point. However, each semester I teach my computer programming students how to point. No, I am not trying to promote rude behavior. Rather, I am teaching my students about pointers, which “point” to another variable or constant.

You yourself may have acted as a pointer in the past. Have you ever been asked where someone lives? If that house was nearby, you may have pointed it out.

The pointer performs a similar function. A pointer points to another variable or constant. Of course, the pointer does not point with an arm and fingers as you would. Rather, the pointer’s value is the address of the variable or constant to which it points. Indeed, you may have done something similar. If you were asked where someone lives and that house was not close enough to physically point out, you instead may have provided an address by which the house could be located.

Pointers have had a reputation among programming students for being difficult to learn. I think that reputation is overblown; pointers are not difficult if you take the time to understand what they do. In any event, difficult or not, it is important to learn about pointers. Some C++ tasks are performed more easily with pointers, and other C++ tasks, such as dynamic memory allocation, cannot be performed without them.

So, on that note, let’s now learn how to create and work with pointers.

Declaring a Pointer

Like any variable or constant, you must declare a pointer before you can work with it. The syntax of declaring a pointer is almost the same as declaring a variable which stores a value rather than an address. However, the meaning of the pointer’s data type is quite different than the meaning of the data type of a variable which stores a value rather than an address.

Syntax of a Pointer Declaration

The syntax of declaring a pointer is almost the same as the syntax of declaring the variables we have worked with in previous chapters. The following statement declares an integer pointer variable: int* iPtr;

The asterisk you use to declare a pointer is the same asterisk that you use for multiplication. However, in this statement the asterisk is being used in a declaration, so in this context it is being used to designate a variable as a pointer. Later in this chapter, we will use the asterisk for a third purpose, as an indirection operator.

NOTE: It is common in C++ for a symbol to have different meanings depending on the context. For example, an ampersand (&) in an argument list means you are passing an argument by reference, whereas an ampersand in front of a variable name is the address operator.

The integer pointer variable also can be declared with the asterisk preceding the variable name instead of following the data type:

int *iPtr;

Either alternative syntax is equally correct because the compiler generally ignores white spaces between an operator and a variable name, constant name, or number. Indeed, the following pointer declaration also works:

int*ptr;

My preference is the first example, in which the asterisk follows the data type and is separated by a white space from the variable name, since (in my opinion) it best signifies that the variable is a pointer. However, all three syntax variations are correct. In any of these variations, the only difference between declaring a pointer variable and a variable which stores a value rather than an address is the asterisk between the data type and the pointer name.

The Meaning of Pointer Data Types

While the syntax of declaring a pointer is almost the same as declaring the variables and constants which store a value rather than an address, the meaning of the data type in the declaration of a pointer is different than in the declaration of those other variables and constants.

With the variables we have worked with previously, the data type in the variable declaration describes the type of data that can be stored in that variable. Thus, the value of an integer variable or constant is an integer, the value of a character variable or constant is a character, and so forth.

However, with a pointer, the data type in the declaration means something different, namely the data type of another variable (or constant) whose memory address is the value of the pointer. In other words, the value of an integer pointer must be the address of an integer variable or constant, the value of a float pointer must be the address of a float variable or constant, and so forth.

The actual data type of the value of all pointers, whether integer, float, character, or otherwise, is the same, a long hexadecimal number that represents a memory address. The only difference between pointers of different data types is the data type of the variable or constant that the pointer points to. This is demonstrated by the following program, which uses the sizeof operator to show that the sizes of pointers of different data types are the same (a long data type uses 4 bytes on my operating system and compiler) even though the different data types (int, float, char) are not all the same size:

#include <iostream> using namespace std;

int main ()
{
  int* iPtr;
  float* fPtr;
  char *cPtr;
  cout << “The size of iPtr is ” << sizeof(iPtr) << endl;
  cout << “The size of fPtr is ” << sizeof(fPtr) << endl;
  cout << “The size of cPtr is ” << sizeof(cPtr) << endl;
  return 0;
}

The output is therefore:

The size of iPtr is 4
The size of fPtr is 4
The size of cPtr is 4

Otherwise, a pointer is similar to the variables or constants we have studied previously. A pointer itself may be a variable or a constant, and like other variables or constants, it is also stored at a memory address. What distinguishes a pointer is that its value is the memory address of another variable or constant.

Assigning a Value to a Pointer

This section will explain how you assign a value to a pointer. Though, before I explain how, perhaps I should explain why.

Why You Should Not Try to Use an Unassigned Pointer

Back in elementary school we were taught a verse: “I shot an arrow into the air, where it lands, I don’t care.” Looking back, I wonder why young children were taught this verse. It may rhyme, but its message is really not appropriate for little ones. However, when you declare a pointer but then use it without first assigning it a value, you are, alas, doing the programming equivalent of that verse.

The following program declares a pointer and then attempts to output its value without first assigning it a value:

#include <iostream>> using namespace std;

int main ()
{
  int* iPtr;
  cout << “The value of iPtr is ” << iPtr << endl;
  return 0;
}

The result, depending on your compiler and operating system, may be a compiler error, a runtime error, or a computer that locks up. Regardless, attempting to use a declared pointer without first assigning it a value is not a good idea.

As you may recall from previous chapters, when you declare a variable and then attempt to output its value without first assigning it a value, the result is a so-called “garbage value” that makes little sense. The reason for this result is that the computer attempts to interpret whatever value is left over from previous programs at the address of the variable.

When the variable is a pointer, that leftover value is interpreted as another memory address, which the pointer then tries to access when you attempt to use it. There are a number of memory address ranges that you are not permitted to access programmatically, such as those reserved for use by the operating system. If the leftover value is interpreted as one of those prohibited addresses, the result is an error.

Null Pointers

If it is too early in your code to know which address to assign to the pointer, then you first assign the pointer NULL, which is a constant with a value of zero defined in several standard libraries, including iostream. The following program does so:

#include<iostream <iostream>>
using namespace std;

int main ()
{
  int* iPtr;
  iPtr = NULL; 
  cout << “The value of iPtr is ” << iPtr << endl;
  return 0;
}

NOTE: You also could use initialization instead of declaration followed by assignment, thus combining the first two statements in main to int* iPtr = NULL.

The resulting output is

The address of x using iPtr is 00000000

A pointer that is assigned NULL is called a null pointer.

On most operating systems, programs are not permitted to access memory at address 0 because that memory is reserved by the operating system. You may now be thinking: “Wait a minute! He just told me how bad it was to risk having pointers point to memory addresses reserved by the operating system. Now he’s having us do that on purpose.”However, the memory address 0 has special significance; it signals that the pointer is not intended to point to an accessible memory location. Thus, if it is too early in your code to know which address to assign to a pointer, you should first assign the pointer to NULL, which then makes it safe to access the value of a pointer before it is assigned a “real” value such as the address of another variable or constant.

{mospagebreak title=Assigning a Pointer the Address of a Variable or Constant}

Let’s now assign a pointer a “real” value, the address of another variable or constant. To do so, you need to access the address of the variable or constant before you can assign that address to the pointer. You use the address operator, covered in Chapter 3, to accomplish this task.

The following program shows how to use the address operator to assign the address of a variable to a pointer. This program also demonstrates that the value of a pointer is the same as the address to which the pointer points.

#include <iostream>> using namespace std;

int main ()

  int num = 5;
  int* iPtr = &num;
  cout << “The address of x using &num is ” << &num << endl;
  cout << “The address of x using iPtr is ” << iPtr << endl;
  return 0;
}

The output on my computer (the following addresses likely will be different on yours) is

The address of x using &num is 0012FED4
The address of x using iPtr is 0012FED4


Figure 11-1 shows graphically how the pointer points to the integer variable.

Indirection Operator and Dereferencing

The primary use of a pointer is to access and, if appropriate, change the value of the variable that the pointer is pointing to. In the following program, the value of the integer variable num is changed twice.

#include <iostream>> using namespace std;

int main ()
{
  int num = 5;
  int* iPtr = &num;
  cout << “The value of num is ” << num << endl;
  num = 10;
  cout << “The value of num after num = 10 is ”
<< num << endl;
  *iPtr = 15; 
  cout << “The value of num after *iPtr = 15 is ”
<< num << endl;
  return 0;
}

The resulting output is

The value of num is 5
The value of num after num = 10 is 10
The value of num after *iPtr = 15 is 15

The first change should be familiar, by the direct assignment of a value to num, such as num=10. However, the second change is accomplished a new way, using the indirection operator:

*iPtr = 15;

The indirection operator is an asterisk, the same asterisk that you used to declare the pointer or to perform multiplication. However, in this statement the asterisk is not being used in a declaration or to perform multiplication, so in this context it is being used as an indirection operator.

NOTE: As mentioned earlier in this chapter, this is another example of a symbol having different meanings in the C++ programming language depending on the context in which it was used.

The placement of the indirection operator before a pointer is said to dereference the pointer. Indeed, some texts refer to the indirection operator as the dereferencing operator. The value of a dereferenced pointer is not an address, but rather the value at that address—that is, the value of the variable that the pointer points to.

For example, in the preceding program, iPtr’s value is the address of num. However, the value of iPtr dereferenced is the value of num. Thus, the following two statements have the same effect, both changing the value of num:

num = 25;
*iPtr = 25;

Similarly, a dereferenced pointer can be used in arithmetic expressions the same as the variable to which it points. Thus, the following two statements have the same effect:

num *= 2;
*iPtr *= 2;

In these examples, changing a variable’s value using the indirection operator rather than through a straightforward assignment seems like an unnecessary complication. However, there are instances covered later in this chapter, such as looping through an array using a pointer, or using dynamic memory allocation, in which using the indirection operator is helpful or even necessary.

The Pointer as a Variable or a Constant

A pointer may be a variable or a constant. Let’s examine both possibilities.

Pointer as a Variable

The preceding program had the pointer pointing to one integer variable. However, a pointer variable, being a variable, can point to different variables at different times in the program. In the following program, the value of the pointer is changed to point to two different integer variables. 

#include <iostream>> using namespace std;

int main ()
{
  int num1 = 5, num2 = 14;
  int* iPtr = &num1;
  cout << “The value of num1 is ” << num1 << endl;
  *iPtr *= 2;
  cout << “The value of num1 after *iPtr *= 2 is ”
<< *iPtr << endl;
  iPtr = &num2;
  cout << “The value of num2 is ” << num2 << endl;
  *iPtr /= 2;
   cout << “The value of num after *iPtr /= 2 is ”
<< *iPtr << endl;
  return 0;
}

The resulting output is therefore:

The value of num1 is 5
The value of num1 after *iPtr *= 2 is 10
The value of num2 is 14
The value of num after *iPtr /= 2 is 7

{mospagebreak title=The Array Name as a Constant Pointer}

While the pointer may be a variable, it also may be a constant. Indeed, in the previous chapter we actually discussed a constant pointer: the name of an array.

As you may recall from Chapter 10, the value of the name of an array is the base address of the array, which also is the address of the first element of an array. Thus, in the following program, both testScore and &testScore[0] have the same value.

#include <iostream> using namespace std; const int MAX = 3;

int main ()
{
  int testScore[MAX] = {4, 7, 1};
  cout << “The address of the array using testScore is ”
<< testScore << endl;
  cout << “The address of the first element of the array ” “using &testScore[0] is ” << &testScore[0] << endl;
  cout << “The value of the first element of the array ” “using *testScore is ” << *testScore << endl;
  cout << “The value of the first element of the array ” “using testScore[0] is ” << testScore[0] << endl;
  return 0;
}

The resulting output is

The address of the array using testScore is 0012FECC
The address of the first element of the array using &testScore[0] is 0012FECC
The value of the first element of the array using *testScore is 4
The value of the first element of the array using testScore[0] is 4

Similarly, if you dereference the name of an array, its value is the same as the value of the first element of the array. Therefore, in the preceding program, both *testScore and testScore[0] have the same value.

However, you cannot change the value of the name of the array. For example, a statement such as testScore++ would result in a compiler error, the error message being “++ needs l-value.”As you may recall from Chapter 10, the term l-value refers to the value to the left of the assignment operator. This error message is another way of saying you can’t increment a constant because that would be changing the value of a constant after you declare it.

Pointer Arithmetic

The value of a pointer, even though it is an address, is a numeric value. Therefore, you can perform arithmetic operations on a pointer just as you can a numeric value.

Using a Variable Pointer to Point to an Array

Pointer arithmetic is done often with arrays. However, since you cannot change the value of the name of an array, it being a constant pointer, you first should declare a variable pointer and then assign it to the address of an array.

So, we begin with an established point of reference, let’s start with the following program, which outputs the address and value at each element of an array using the name of the array:

#include <iostream>> using namespace std; const int MAX = 3;

int main ()
{ int testScore[MAX] = {4, 7, 1};
  for (int i = 0; i < MAX; i++)
  {
    cout << “The address of index ” << i << ” of the array is “<< &testScore[i] << endl;
    cout << “The value at index ” << i
<< ” of the array is “<< testScore[i] << endl;
  }
  return 0;
}

The resulting output is

The address of index 0 of the array is 0012FECC
The value at index 0 of the array is 4
The address of index 1 of the array is 0012FED0
The value at index 1 of the array is 7
The address of index 2 of the array is 0012FED4
The value at index 2 of the array is 1

This program used the name of the array, testScore, to access, by index, each element of the array. The name of the array is a constant pointer. The following program modifies the previous program by using a variable pointer, iPtr, to access by index each element of the array.

#include <iostream>> using namespace std; const int MAX = 3;

int main ()
{
  int testScore[MAX] = {4, 7, 1};
  int* iPtr = testScore;
  for (int i = 0; i < MAX; i++)
  {
    cout << “The address of index ” << i << ” of the array is “<< & iPtr[i] << endl;
    cout << “The value at index ” << i << ” of the array is “<< iPtr[i] << endl;
 
}
 
return 0; 
}

The following statement in this program sets the variable pointer iPtr to point to the same address as the array name testScore:

int* iPtr = testScore;

The array name is not preceded with the address operator (&) because the array name already is an address, namely, the base address of the array. Therefore, after this assignment, iPtr and testScore both point to the beginning of the array. Accordingly, as shown in Figure 11-2, iPtr[2] and testScore[2] have the same value.

{mospagebreak title=Incrementing a Pointer}

An important reason for declaring a variable pointer so it points to the same address as the array name is so the variable pointer can be incremented, unlike the array name which cannot be incremented because it is a constant pointer. The fol-lowing program increments the variable pointer to access each succeeding element of the array:


Figure 11-2.  Variable and constant pointers used to access array elements

#include <iostream>> using namespace std; const int MAX = 3;

int main ()
{
  int testScore[MAX] = {4, 7, 1};
  int* iPtr = testScore;
  for (int i = 0; i < MAX; i++, iPtr++)
  {
    cout << “The address of index ” << i << ” of the array is “<< iPtr << endl;
    cout << “The value at index ” << i << ” of the array is “<< *iPtr << endl;
  }
  return 0;
}

Incrementing an integer variable increases its value by 1. However, incrementing a pointer variable increases its value by the number of bytes of its data type. This is an example of pointer arithmetic. When you run this program, the first address outputted is 0012FECC, the second 0012FED0, and the third 0012FED4. These hexadecimal addresses are 4 bytes apart because, on the compiler and operating system used by me to run this program, the integer data type takes 4 bytes.

For this reason, as shown in Figure 11-3, iPtr + 1 is not the base address plus 1, but instead is thebaseaddress+4. Thesameistrueof testScore + 1. Consequently, the value at the second element of the array can be expressed one of four ways:

  • testScore[1];
  • *(testScore + 1);
  • iPtr[1];
  • *(iPtr + 1);

 

Comparing Addresses

Addresses can be compared like any other value. The following program modifies the previous one by incrementing the variable pointer so long as the address to which it points is either less than or equal to the address of the last element of the array, which is &testScore[MAX - 1]:

#include <iostream>> using namespace std; const int MAX = 3;
int main ()
{
  int testScore[MAX] = {4, 7, 1};
  int* iPtr = testScore;
  int i = 0;
  while (iPtr <= &testScore[MAX - 1])
  {
    cout << “The address of index ” << i << ” of the array  is “<< iPtr << endl;
    cout << “The value at index ” << i << ” of the array is “<< *iPtr << endl;
    iPtr++;
    i++;
  }
  return 0;
}

As Figures 11-2 and 11-3 depict, the comparison to &testScore[MAX-1] instead could have been made to testScore + MAX – 1.

Decrementing a Pointer

The same considerations apply to decrementing a pointer, which decreases its value by the number of bytes of its data type. Decrementing a pointer can be used to step “backwards” through an array. 

#include <iostream>> using namespace std; const int MAX = 3;

int main ()
{
  int testScore[MAX] = {4, 7, 1};
  int* iPtr = &testScore[MAX - 1];
  int i = MAX – 1; while (iPtr >= &testScore[0])
  {
    cout << “The address of index ” << i << ” of the array is “<< iPtr << endl;
    cout << “The value at index ” << i
<< ” of the array is “<< *iPtr << endl;
    iPtr–;
    i–;
  }
  return 0;
}

The output is therefore

The address of index 2 of the array is 0012FED4
The value at index 2 of the array is 1
The address of index 1 of the array is 0012FED0
The value at index 1 of the array is 7
The address of index 0 of the array is 0012FECC
The value at index 0 of the array is 4

The key statement is

int* iPtr = &testScore[MAX - 1];

This statement has the variable pointer point to the last address in the array. That address then is decremented in the loop so that the pointer variable points to the preceding address in the array. The loop continues so long as the address pointed to by the pointer variable is not before the base address of the array.

As discussed previously, the pointer variable also could have been initialized as follows:

int* iPtr = testScore + MAX – 1;

Pointers as Function Arguments

Pointers may be passed as function arguments. Pointer notation usually is used to note that an argument is a pointer. However, if the pointer argument is the name of an array, subscript notation alternatively may be used. 

{mospagebreak title=Passing an Array Using Pointer Notation}

In Chapter 10, we employed the following program that used one function to assign values to the array and another function to display values from the array, rather than doing all that work in the main function. 

#include <iostream>
using namespace std;
void assignValues(int[], int);
void displayValues(int[], int);
const int MAX = 3;

int main ()
{
  int testScore[MAX];
  assignValues(testScore, MAX);
  displayValues(testScore, MAX);
  return 0;
  }

  void assignValues(int tests[], int num)
  {
    for (int i = 0; i < num; i++)
      {
cout << “Enter test score #” << i + 1 << “: “; cin >> tests[i];
      }
  }

  void displayValues(int scores[], int elems)
 
{
    for (int i = 0; i < elems; i++)
     
{
        cout << “Test score #” << i + 1 << “: ” << scores[i] << endl;
  }
}

As discussed in Chapter 10, the two functions, assignValues and displayValues, passed their first argument, the array, by address. An argument passed by address can be changed in the calling function (here main) by the called function (here assignValues) just as if the argument had been passed by reference. Thus, the assignValues function changed the value of the testScore array in main by assigning values to the elements of that array.

The function prototypes and headers of the assignValues and displayValues functions used a subscript [] to indicate that an array is being passed. However, you can also use pointer notation—for instance, asterisk * instead of a subscript []—as the following example demonstrates:

#include <iostream>
using namespace std;
void assignValues(int*, int);
void displayValues(int*, int);
const int MAX = 3;

int main ()
{
  int testScore[MAX]; assignValues(testScore, MAX);
  displayValues(testScore, MAX);
  return 0;
}

void assignValues(int* tests, int num)
{
  for (int i = 0; i < num; i++)
  { 
    cout << “Enter test score #” << i + 1 << “: “;
    cin >> tests[i];
  }
}

void displayValues(int* scores, int elems)
{
  for (int i = 0; i < elems; i++)
  {
    cout << “Test score #” << i + 1 << “: ” << scores[i] << endl;
  }
}

The following comparison of the prototypes of the assignValues function using subscript and pointer notation, respectively, shows that the only difference is whether a subscript [] or an asterisk * is used to denote that the argument is an array:

void assignValues(int[], int);
void assignValues(int*, int);

Similarly, the following comparison of the function headers of the assignValues function using subscript and pointer notation, respectively, shows that the only difference is whether a subscript [] or an asterisk * is used to denote that the argument is an array. This time, however, the asterisk precedes the variable name, whereas the subscript follows the variable name.

void assignValues(int tests[], int num)
void assignValues(int* tests, int num)

Whether you use subscript or pointer notation to pass an array really is a matter of preference. There is no programming advantage one way or the other. However, the next section discusses a situation in which subscript notation is not an option, so pointer notation is the only choice.

Passing a Single Variable Using Pointer Notation

Passing an array name by address is relatively simple because the value of the array name is an address. However, you may often want to pass a single variable by address. By single variable I don’t mean a variable that is unmarried, but instead, for example, an int as opposed to an int array. 

With a single variable, subscript notation is not an option. Subscripts make sense only with an array. Rather, you need to use pointer notation to pass a single variable by address.

Passing an argument by reference or by address both enable the passed argument to be changed in the calling function by the called function—only the syntax is different. For comparison, let’s start with the following program from Chapter 9 that passes the variable to be doubled by reference:

#include <iostream <iostream>>
using namespace std;
void doubleIt(int&);

int main ()
{
int num;
  cout << “Enter number: “;
  cin >> num;
  doubleIt(num);
  cout << “The number doubled in main is ” << num << endl;
  return 0;
}

void doubleIt (int& x)
{
  cout << “The number to be doubled is ” << x << endl;
  x*=2;
  cout << “The number doubled in doubleIt is ” << x << endl;
}

Here is some sample input and output:

Enter number: 3
The number to be doubled is 3
The number doubled in doubleIt is 6
The number doubled in main is 6

Let’s now modify this program so it passes the variable to be doubled by address instead of by reference:

#include <iostream>
using namespace std;
void doubleIt(int*);

int main ()
{
  int num;
  cout << “Enter number: “;
  cin >> num;
  doubleIt(&num);
  cout << “The number doubled in main is ” << num << endl;
  return 0;
}

void doubleIt (int* x)
  {
  cout << “The number to be doubled is ” << *x << endl;
  *x *= 2;
  cout << “The number doubled in doubleIt is ” << *x << endl;
}

There are four syntax differences between these two programs.

1. In the function prototype, you use an ampersand (&) for passing by reference but an asterisk (*) for passing by address:

void doubleIt(int&); // by reference
void doubleIt(int*); // by address

2. Similarly, in the function header, you use an ampersand (&) for passing by reference but an asterisk (*) for passing by address:

void doubleIt (int& x) // by reference
void doubleIt (int* x) // by address

3. When you call the function, you don’t need the address operator (&) for passing by reference, but you do need one for passing by address since you are supposed to be passing by the address of x.:

doubleIt(num); // by reference
doubleIt(&num); // by address

4. In the body of the called function, you don’t need to dereference the argument when you pass it by reference, but you do need to when you pass by address since x, being passed by address, is not a value but is instead a pointer:

// by reference -no dereference

  cout << “The number to be doubled is ” << x << endl;
  x*=2;
  cout << “The number doubled in doubleIt is ” << x << endl;
}
// by address -need to dereference
{
  cout << “The number to be doubled is ” << *x << endl;
  *x *= 2;
  cout << “The number doubled in doubleIt is ” << *x << endl;
}

You may legitimately be wondering why, with a single variable argument, I would want to pass it by address when the syntax for passing it by reference seems easier. The pat answer I give my students is that there are certain sadistic computer science teachers (I’m not mentioning any names here) who insist their students pass by address to make them suffer. All kidding aside though, there are actually certain library functions that do use pass by address. Additionally, when using dynamic memory allocation and returning pointers from functions (to be covered in the following sections), passing by address may be the only option.

{mospagebreak title=Dynamic Memory Allocation}

As discussed in Chapter 10, when declaring an array, the size declarator must be either a literal or a constant, and may not be a variable. The following program from Chapter 10 attempts, unsuccessfully, to use a variable numTests in declaring the size of an array:

#include <iostream <iostream>>
using namespace std;
int main ()
{
  int numTests;
  cout << “Enter the number of test scores:”;
  cin >> numTests;
  int testScore[numTests];
  return 0;
}

The result is a compiler error. The compiler will flag the declaration of the array (int testScore[numTests]) and complain that a constant expression was expected. The reason a constant (or literal) expression is required is that in this program we are allocating memory for the array at compile time. The compiler needs to know exactly how much memory to allocate. However, if a variable is the size declarator, the compiler does not know how much memory to allocate because a variable’s value may change. Indeed, in the preceding example, the value of the variable used as the size declarator is not even known until runtime.

Having said this though, it is often desirable to have the user determine at runtime the size of the array so it is neither too small nor too large, but just right. To accomplish this, you need to declare the array using dynamic memory allocation. The following program modifies the previous one to use dynamic memory allocation:

#include <iostream <iostream>>
using namespace std;
int main ()
{
  int numTests;
  cout << “Enter the number of test scores:”;
  cin >> numTests;
  int * iPtr = new int[numTests];
  for (int i = 0; i < numTests; i++)
  {
    cout << “Enter test score #” << i + 1 << ” : “;
    cin >> iPtr[i];
  }
  for (i = 0; i < numTests; i++)
  cout << “Test score #” << i + 1 << ” is ” << iPtr[i] << endl;
  delete [] iPtr;
  return 0;
}

Some sample input and output follows:

Enter the number of test scores: 3
Enter test score #1: 66
Enter test score #2: 88
Enter test score #3: 77
Test score #1 is 66
Test score #2 is 88
Test score #3 is 77

Dynamic memory allocation works, even though using a variable as a size declarator does not, because with dynamic memory allocation, memory is not being allocated at compile time. Instead, memory is being allocated at runtime, and from a different place (the “heap”) rather than where it is allocated at compile time (the “stack”).

NOTE: The terms heap and stack also have meaning in data structures. Here, however, these terms are used to identify different areas of memory: the stack for memory allocated at compile time; the heap for memory allocated at runtime.

While you could dynamically allocate a single variable, normally dynamic memory allocation is used with arrays (as in this example), or with objects such as structures or classes, which are discussed in Chapter 14.

You need to use a pointer to dynamically allocate memory. The pointer must be of the same data type as the array that is to be allocated dynamically. An assignment statement is used, as in the following statement from the program:

int * iPtr = new int[numTests];

The pointer is on the left side of the assignment operator. Immediately to the right of the assignment statement is the new operator, whose purpose is to dynamically allocate memory. The array that is to be allocated dynamically immediately follows the new operator, described by data type and a size declarator in a subscript [], but with no array name. The size declarator may be a variable instead of a literal or constant.

Since the array has no name, it and its elements are referred to through the pointer that created it, such as iPtr[i] in the for loops used to assign values to and output the values of the array elements. Therefore, the scope of the dynamically created array is the same as the scope of the pointer used to declare it.

The significance of dynamic memory allocation is not scope, but lifetime. Like a global variable or a static local variable, the lifetime of a dynamically created variable is as long as that of the program’s execution. However, if before the end of the program the pointer that points to a dynamically created variable goes out of scope, you no longer have any way of accessing the dynamically created memory. Therefore, the dynamically created variable still takes up memory, but is inaccessible. This is called a memory leak.

Having programs that dynamically allocate memory but never release it is akin to a library where patrons check out books but never return them. Sooner or later the library will run out of books, and the computer will run out of memory.

A memory leak is not a particular concern in the preceding program since the pointer that points to the dynamically allocated memory does not go out of scope until immediately before the program ends. However, if you dynamically allocate memory inside a function using a local pointer (as in a program in the next section), then when the function terminates, the pointer will be destroyed but the memory will remain, orphaned since there is no longer a way of accessing it for the remainder of the program.

You release dynamically allocated memory with the delete operator. Just as the new operator is used to create dynamically allocated memory, the delete operator is used to return dynamically allocated memory to the operating system. The syntax is the delete operator followed by the pointer that points to the dynamically created memory. Additionally, if the dynamically created memory is an array as opposed to a single variable, then empty subscripts [] are placed between the delete operator and the pointer, as in the following statement from the program:

delete [] iPtr;

While the delete operator operates on a pointer, the delete operator does not delete the pointer. Instead, the delete operator deletes the memory at the address pointed to by the pointer.

NOTE: You should only use the delete operator with a pointer that points to dynamically created memory. Using the delete operator with a pointer that points to memory created on the stack rather than from the heap can lead to unpredictable results.

Finally, since the pointer is the only way to which you can refer to the dynamically allocated variable, you should not change the value of the pointer to point to a different address unless you first assign a different pointer to the dynamically allocated memory. Otherwise, you no longer have a way of accessing the dynamically created memory. The result would be a memory leak.

{mospagebreak title=Returning Pointers from Function s}

In Chapter 10, you learned several ways to initialize a character array. The following program shows you an additional way:

#include <iostream <iostream>>
using namespace std;
char * setName();

int main (void)
{
  char* str = “Jeff Kent”;
  cout << str;
  return 0;
}

With some sample input and output:

Enter your name: Jeff Kent
Your name is Jeff Kent

The key statement is

char* str = “Jeff Kent”;

This statement is almost the same as:

char str[] = “Jeff Kent”;

In both statements, str is a character pointer, and implicit array sizing is used. The difference is that str in the first statement (char* str) is a variable pointer whereas str in the second statement (char str[]) is a constant pointer.

Returning a Pointer to a Local Variable (Not a Good Idea)

Now, following the advice in Chapter 9 to make your program more modular, you try to write a separate function, setName, to obtain the user input. The setName function creates a character array, assigns user input to that array using the getline function of the cin object, and then returns a pointer to that character array. The address which is returned by the setName function then is assigned to the character pointer str in main. The following program implements this concept:

#include <iostream>
using namespace std;

char * setName();

int main (void)
{
  char* str = setName();
  cout << str;
  return 0;
}

char* setName (void)
{
  char name[80];
  cout << “Enter your name: “; cin.getline (name, 80);
  return name;
}

The following is some sample input and output:

Enter your name: Jeff Kent
………………D ………… ……..8 ……. .

While the outputted name is interesting, it certainly would be difficult to write, and in any event, it is not what I inputted. What went wrong is that the pointer returned by the setName function points to a local character array whose lifetime ended when that function finished executing and returned control to main.

Accordingly, the indicated solution is to extend the lifetime of that character array to the life of the program execution itself. Of course, one way to accomplish this is by making the character array a global variable, but as you should recall from Chapter 9, there are other and better alternatives.

Returning a Pointer to a Static Local Variable

One superior alternative is to make the character array in setName static, as in the following program: 

#include <iostream <iostream>>
using namespace std;
char * setName();

int main (void)
{
  char* str = setName();
  cout << str;
  return 0;
}

char* setName (void)
{
  static char name[80];
  cout << “Enter your name: “; cin.getline (name, 80);
  return name;
}

The output from the following sample input now looks much better:

Enter your name: Jeff Kent
Jeff Kent

This works because while the scope of a static local value is limited to the function in which it is declared, its lifetime is not similarly limited, but instead lasts as long as the execution of the program. Therefore, the pointer returned by the setName function points to a local character array whose lifetime, since it was declared with the static keyword, persisted after the setName function finished executing and returned control to main.

Returning a Pointer to a Dynamically Created Variable

Another alternative, which you learned in this chapter, is dynamic memory allocation: 

#include <iostream>
using namespace std;
char * setName();

int main (void)
{
  char* str;
  str = setName();
  cout << str;
  delete [] str;
  return 0;
}

char* setName (void)
{
  char* name;
  name = new char[80];
  cout << “Enter your name: “;
  cin.getline (name, 80);
  return name;
}

This works because the pointer returned by the setName function points to a character array whose lifetime, since it was declared using dynamic memory allocation, persisted after the setName function finished executing and returned control to main.

As discussed in the previous section, if you dynamically allocate memory inside a function using a local pointer, then when the function terminates, the pointer will be destroyed but the memory will remain, orphaned since there is no longer a way of accessing it for the remainder of the program. This problem is avoided in this program since the local pointer’s address is returned by the setName function and is assigned in main to another pointer variable, str. The pointer variable str then is used at the end of main with the delete operator to deallocate the character array which was dynamically allocated in the setName function.

This is an example where different pointers point to the same memory address. However, in the case of dynamically created memory, once you use one of those pointers with the delete operator, don’t make the common mistake of using another of the pointers with the delete operator. You should only use the delete operator with a pointer that points to dynamically created memory. If that dynamically allocated memory already has been deallocated using the delete operator, using the delete operator the second time will lead to unpredictable results.

{mospagebreak title=Summary}

A pointer is a variable or constant whose value is the address of another variable or constant. Some C++ tasks are performed more easily with pointers, while other C++ tasks, such as dynamic memory allocation, cannot be performed without pointers.

Like any variable or constant, you must declare a pointer before you can work with it. The only difference between declaring a pointer and a variable or constant which stores a value instead of an address is that the pointer declaration includes an asterisk between the data type and the pointer name. However, the data type in the declaration of a pointer is not the data type of its value, as is the case with a variable or constant which stores a value instead of an address. The actual data type of the value of all pointers, whether integer, float, character, or otherwise, is the same, a long hexadecimal number that represents a memory address. Rather, the data type in the declaration of a pointer refers to the data type of another variable (or constant) whose memory address is the value of the pointer. In other words, the value of an integer pointer variable must be the address of an integer variable or constant, the value of a float pointer variable must be the address of a float variable or constant, and so forth.

You should always explicitly assign a pointer a value before you use it; otherwise, you risk a runtime error or worse. When you are ready to assign a pointer the address of another variable or constant, you use the address operator with the target variable or constant. However, if it is too early in your code to know which address to assign to the pointer, you first assign the pointer NULL, which is a constant defined in several standard libraries, including iostream. The value of NULL, the memory address 0, signals that the pointer is not intended to point to an accessible memory location.

The indirection operator is used on a pointer to obtain the value of the variable or constant to which the pointer points. This operation is said to dereference the pointer.

A pointer may be a variable or a constant. The name of an array is a constant pointer, pointing to the base address of the array. A pointer variable, being a variable, may point to different variables or constants at different times in the program.

A pointer variable may be incremented. Incrementing a pointer variable is common when looping through consecutive indices of an array. Incrementing a pointer variable does not necessarily increase its value by 1. Instead, incrementing a pointer variable increases its value by the number of bytes of its data type.

Pointers may be passed as function arguments. This is called passing by address. Pointer notation usually is used to note that an argument is a pointer. The difference in syntax between passing by reference and passing by address is that, in the function prototype and header, you use an ampersand (&) for passing by reference, but an asterisk (*) for passing by address. Additionally, if the pointer argument is the name of a single variable as opposed to an array, there are two further differences in syntax between passing by reference and passing by address. First, when you call the function, you don’t need the address operator (&) for passing by reference, but you do for passing by address. Second, in the body of the called function, you don’t need to dereference the argument when you pass it by reference, but you do when you pass by address.

You can use a variable as a size declarator for an array if you use dynamic memory allocation because memory is allocated at runtime from a different place—the heap—than where memory is allocated for variables declared at compile time on the stack. You need to use a pointer with the new operator to dynamically allocate memory, and the pointer must be of the same data type as the array which is to be allocated dynamically.

The lifetime of a dynamically allocated variable may be as long as that of the pro-gram’s execution. However, if before the end of the program the pointer that points to a dynamically created variable goes out of scope, you no longer have any way of accessing the dynamically created memory. Therefore, the dynamically created variable still takes up memory, but is inaccessible. This is called a memory leak. To avoid memory leaks, you use the delete operator on the pointer that points to the dynamically allocated memory. This deallocates the dynamically allocated memory.

The return value of a function may be a pointer. If so, the pointer should point to either a static local variable or a dynamically created variable, not a local variable.

Quiz
  1. What is a pointer?
  2. Name a C++ task that requires a pointer to be performed.
  3. What is the difference between declaring an integer variable and declaring an integer pointer variable?
  4. What is the meaning of the data type in the declaration of a pointer?
  5. What is the meaning and purpose of NULL?
  6. What operator do you use to assign a pointer the address of another variable or constant?
  7. What is the purpose of the indirection operator?
  8. May a pointer point to different memory addresses at different times in the program?
  9. May more than one pointer point to the same memory address?
  10. What is the effect of incrementing a pointer variable?
  11. What are the purposes of the new and delete operators?
[gp-comments width="770" linklove="off" ]

chat sex hikayeleri Ensest hikaye