If an array such as int a[10] means that a is assigned the address of a[0], the first entry of the array, could we not use pointers in a similar way, that is, simply assign the pointer the address of the first entry of the array. We can do this with new, just like we did when we dynamically allocated memory, however, we can also specify a size, as is shown in Program 1.
Program 1. Dynamically allocated array.
#include <iostream> using namespace std; int main() { int * ptr = 0; // allocate memory for 20 integers (80 bytes) ptr = new int[20]; for ( int i = 0; i < 20; ++i ) { ptr[i] = i*i; } for ( int i = 0; i < 20; ++i ) { cout << "The square of " << i << " is << ptr[i] << endl; } // must clean it up delete[] ptr; ptr = 0; return 0; }
The two new features in Program 1 are similar to the features shown in Pointers into the New World, only now, instead of asking for the memory for one integer, we are now asking for enough memory for 20 integers. The operating system (OS) will search for 80 contiguous bytes in memory. The new[] operator will then return the address of the first byte.
Once the memory has been allocated, you can now use the pointer as if it was an array, as is demonstrated in Program 1.
You will notice that instead of calling delete, because we allocated the memory using new[], we had to use a call to the operator delete []. A call to new Type() calls the constructor once, whereas a call to new Type[10] calls the constructor 10 times, once for each entry in the array. Calling delete calls the destructor only once, whereas calling delete [] would call the destructor for each entry in the array being deleted.
In Program 1, I explicitly called the variable int * ptr. By convention, if a pointer is to be used as an array, it is usually has the word array appearing in the variable name, or perhaps the plural of what it is storing.
You may use Program 2 to convince yourself that the memory allocated is indeed contiguous, and you will note that the address of the pointer remains unchanged, only the value of the pointer is changed. Note that we can use new[] in the same line in which the variable is declared.
Program 2. Addresses of dynamically allocated array.
#include <iostream> using namespace std; int main() { // allocate memory for 20 integers (80 bytes) int * array = new int[20]; cout << "The address of 'array' is " << &array << endl; cout << "The value of 'array' is " << *array << endl; for ( int i = 0; i < 20; ++i ) { cout << "The address of array[" << i << "] is " << &( array[i] ) << endl; } // must clean it up delete[] array; array = 0; return 0; }
The size of the array does not have to be fixed at compile time. Program 3 shows how you can dynamically choose the size of the array at run time.
Program 3. Arrays defined by the user.
#include <iostream> using namespace std; int main() { int array_size; double *array = 0; cout << "Enter an array size: " << endl; cin >> array_size; array = new double[array_size]; // we will approximate cos(i/10) for each entry of the array for ( int i = 0; i < array_size; ++i ) { double x = 0.1 * static_cast<double>( i ); double xx = x * x; // this is not the best way to calculate this Taylor series array[i] = 1.0 - xx/2.0 + xx*xx/24.0 - xx*xx*xx/720.0; cout << "cos(" << i << "/10) = " << array[i] << endl; } delete[] array; array = 0; return 0; }
You do not have to, and you should not zero out the entries of an array before you call delete[]. Doing so does not save any memory in C++ what-so-ever. Because C++ does not have any garbage collection facility, the only way to save memory in C++ is to call delete or delete[].
The graphical representation of the array using pointers will be as shown in Figure 1. This shows the dynamically allocated array defined by
int * array = new int[5]; for ( int i = 0; i < 5; ++i ) { array[i] = i*i; } // view image delete [] array; array = 0;
Note the difference between the graphical representation of the array defined using int a[5].
Figure 1. Graphical representation of a dynamically allocated array.
We may make a few observations: a dynamically allocated array requires four extra bytes because the pointer array must also be stored. In the grand scheme, this is often negligible. Also, the memory location of the variable array is different from the memory location of the first entry of the array.
1. Write a program which declares the variable a to be a pointer to an int: int * a = 0;
Next, use new int( 25 ) to allocate memory for a single int and change the value to 36 and print it to the screen. Use delete to free the memory.
Use the same variable, but now use new[25] to allocate memory for 25 ints. Set these 25 entries to the 10 times the index, i.e., a[i] = 10*i;, and print them out. Use delete[] to free the memory.
2. Discuss the serious problems in using the same variable to store either a single instance of a pointer or an array.
3. Run the following code:
int * ptr = new int( 25 ); cout << "The value of *ptr is " << *ptr << endl; cout << "The value of ptr[0] is " << ptr[0] << endl;
What does this suggest about how C++ (and C) treats pointers?
4. Describe the differences between the two declarations:
int * ptr = new int(1);
and
int * ptr = new int[1];
What does this suggest about the requirement to initialize an array?