Okay, bad pun...sorry.
Up until now, every time we've used a pointer, all we've done is used the address of an already-existing variable. This probably doesn't strike you as useful, as we could always modify the variables instead.
In this lesson, we will see how we can declare a pointer and then ask the operating system (OS) for new memory to point to. In Program 1, we declare ptr to be a pointer to a int, however, then we call a new operator: new.
Program 1. Printing the value of a variable and its address.
#include <iostream> using namespace std; int main() { int * ptr = 0; // pointing to nothing cout << "The pointer is initially pointing to 0: " << ptr << endl; ptr = new int( 42 ); // ask for new memory from the OS cout << "The pointer storing the address " << ptr << endl; cout << "The value stored there is " << *ptr << endl; *ptr = 256; cout << "The pointer is still storing the address " << ptr << endl; cout << "The value stored there is now " << *ptr << endl; delete ptr; // give the memory back to the OS ptr = 0; // set the pointer to 0 return 0; } |
The new operator asks the OS for sufficient memory for an int (4 bytes) and to initialize it with the value 42. To view what this looks like, consider Figure 1. When we called new and asked for a new int, the OS searched memory for 4 bytes of unused memory. It then allocated that memory to the running program and returned the address of that memory. In Figure 1, the address 0x03B28CDC is returned and therefore assigned to ptr.
Figure 1. Memory map after calling new int(42).
Using our simplified version with arrows, we could also draw Figure 1 as is shown in Figure 2.
Figure 2. The simplified diagram showing the state after calling new int(42).
The difference between Figure 2 and Figure 2 is that there is no variable associated with the memory storing 42. The only way to change the value is to assign to *ptr.
In Program 1, the second-last line calls delete. Previously, we did not worry about variables: the compiler was aware that we were asking for a variable, and the compiler took care of that memory once we were finished. The same is not true for new: because we explicitly asked for new memory, we must also give that memory back to the OS using the delete operator. This gives us much stricter control over when we allocate (call new) and deallocate (call delete).
This type of request for memory from the OS is called dynamic memory allocation. This means that the compiler is not aware that we are asking for this memory: it simply translates the call to new with an appropriate request to the OS. Only when the program is run does the request actually occur and it is a reasonably expensive request -- after all the OS must search through its available memory, find four bytes, allocate that memory to the running program (process), and return the address of the first byte.
To demonstrate where this is useful, consider Microsoft® Word. Each time you open a new document, this requires new memory. As you keep typing, more and more memory is required. If all the memory was allocated by the compiler, this would require that all the documents would have to be the same size: if you created a document with Hello World! or a copy of the Canadian Forces Administrative Orders (CFAOs -- just imagine how large this bureaucratic document is!!!) all memory would have to be allocated at compile time. One consequence is that there would be a maximum size to a document, and the smallest and largest documents would take up the same amount of memory.
Dynamic memory allocation solves this: the program (in this example, Word) can ask for more memory as is required.
1. Each time you call new, determine where you should call delete, even if you only put something in a comment.
2. If you are not initializing a pointer with a reference to actual memory (either through & or new, set it to zero.
3. Each time you call delete, set the pointer to 0. Thus, if you accidentally try to access that pointer, your program will terminate and indicate that problem exists. If you don't, your program may continue to run, however, you will run into problems later.
1. Write a program which has two pointers ptr01 and ptr02. Allocate new memory for a new int with a value of 71 to the pointer ptr01. Assign the value of ptr01 (the address) to ptr02 and change the value stored to 82. Check your answer by printing *ptr01.
2. In Question 1, should you call delete once or twice?
3. Write a program which allocates a new int for two pointers ptr01 and ptr02. Let the initial values stored in these locations be 42 and 256, respectively. Swap these two values such that the two pointers still point to the same memory location.
4. Like Question 3, write a program which allocates a new int for two pointers ptr01 and ptr02. Let the initial values stored in these locations be 42 and 256, respectively. Swap the pointers so that the the second pointer ptr02 has the address of memory storing 42 and vice versa.
5. Rewrite Program 3 from For Loops so that, instead of using double result = 1; double term = 1;, it uses double * ptr_result = new double(1); double * ptr_term = new double(1); and then print *ptr_result. Be sure to delete the allocated memory afterwards.
Hint: you don't have to, but you can always wrap any dereferencing with parentheses, e.g., (*ptr_term) += ....