Week 11

Introducing the Heap

A memory diagram.

The illustration on the right shows how memory is used when your program runs. Global variables (and constants) are placed in the static storage area (along with the program's code or text), when your program is loaded. These remain in the same location while your program is running. This is called static allocation.

Local variables and parameters are created when encountered during runtime. These are allocated on the stack. When you call a function, memory is allocated for the variable; when the function returns, that memory is freed. This is known as automatic allocation.

Finally, explicitly requesting memory from the operating system as the program runs is called dynamic allocation. The memory for these dynamic variables is allocated on the freestore or heap.

This makes it possible for data structures to expand as your program runs. Classes like vector and string depend on this; when a vector needs more memory, for a push_back() say, it requests it using dynamic allocation.

Week 11

Stack vs. Heap

A memory picture in Java.

You're already familiar with dynamic memory, since that's what is used to create every object in Java. Writing new Scanner(System.in), allocates memory in the heap to store a new Scanner object as shown here.

In C++, however, it is not enough to allocate memory; you also have to free that memory when it is no longer needed. The process of doing this in a disciplined way is called manual memory management.

Allocating memory from the heap is common in programming. All the standard library collection classes use the heap to store their elements. In CS 250 and CS 200, you’ll have many opportunities to build your own versions of these collection classes. Before doing so, it is important to learn the underlying mechanics of dynamic allocation and how the process works.

Week 11

The new Operator

Like Java, C++ uses the new operator to allocate memory on the heap. In its simplest form, the new operator takes a type and allocates space for a single variable of that type located on the heap. For example, look at this code:

Allocating a variable.
int *p = new int;

The new operator returns the address of the variable it allocates. I just ran this on my machine and the first location on the heap was located at address 0x558f24791eb0, so that is what is stored in the variable p.

Knowing the exact address value stored in p is really meaningless; instead, realize that whatever the address, the variable p on the stack always points to the newly allocated int on the heap. That means we can indicate the relationship with an arrow.

To use the object on the heap, just dereference the pointer. To store 42 just write this:

Allocating a variable.
*p = 42;

Using the raw new operator to create an object leaves the variable uninitialized if it is a primitive or built-in type and default initialized for class types. Instead creating uninitialized elements on the heap, you can initialize individual variables by using direct initialization. In C++11, you can also use uniform initialization.

int *p1 = new int;          // uninitialized
int *p2 = new int(42);      // direct initialized
string *p3 = new string;    // default initialized
double *p4 = new double;  // default (c++11)
Week 11

Dynamic Arrays

You can also create arrays on the heap; these are called dynamic arrays. Follow the type name with the desired number of elements enclosed in square brackets:

A new dynamic array.
double *da = new double[3];

Here, da points to the first double in a block of memory large enough to hold three doubles. Treat da as an array whose storage lives in the heap rather than on the stack. Remember to save the size of the array, though, since, you cannot use the "sizeof trick" on a dynamic array.

Before C++ 11, elements of a dynamic array were uninitialized. With the new standard, you can value initialize (that is, set to zero for primitive types), all the elements on the heap by using a set of empty parentheses after the square brackets.

A new dynamic array zero initialized.
double *da = new double[3]();

Now, each element pointed to by the da pointer is guaranteed to hold 0.0.

You can also use list-style uniform initialization to partly initialize a dynamic array:

A new dynamic array initialized.
double *da = new double[3]{1, 2};
Week 11

Dynamic Objects

The new operator can also allocate objects or structures on the heap. Let's assume you have a Fraction class. If you use only the class name, then C++ allocates space for a default Fraction object on the heap.

Fraction *fp = new Fraction;

If Fraction has a default constructor, the new operator automatically calls it, passing the address of the newly allocated memory. Assuming the constructor initializes each data member to the values 0/1, you'll end up with something like this:

Creating a Fraction on the heap.

Supply arguments, and C++ will call the matching overloaded constructor.

Week 11

Freeing Memory

Computer memory is finite, so the heap may eventually run out of space. When this occurs, the new operator throws a bad_alloc exception. There is usually nothing the program can do to recover. With a modern O/S and virtual memory, this is very rare.

Unlike Java, C++ programmers must manually free heap variables when they are no longer used. In Java, this is handled by the garbage collector. In C++ you free a heap variable by using the delete operator like this:

Fraction *fp = new Fraction;
// Use the fraction object here
delete fp;  // free the memory

When you allocate an array, you add square brackets after the delete keyword, like this:

double *da = new double[3]; 
// Use the array
delete[] da;    // free heap memory

Click on the link here to visualize several uses of pointers.