Part B - Foundations
Dynamic Memory
Describe the system memory under the operating system's control
Introduce the syntax for allocating and deallocating dynamic memory
Describe common issues that may arise with dynamic memory
"Avoid allocating and deallocating in different modules" (Sutter, Alexandrescu, 2005)
Memory
| Allocation
| Deallocation
| Issues
| Single Instances
| Operator Review
| Summary
| Exercises
The system memory that an application uses may vary with the size of the
user's problem. C++ does not require programmers to specify all of the
memory required at compile-time. By designing an application so that
it determines its memory requirements at run-time, we create a more flexible
programming solution.
This chapter describes the syntax for allocating and deallocating
memory dynamically at run-time, including the operators for these
tasks. The chapter concludes with a summary of the operators
used in these notes and their order of precedence in evaluating
compound expressions.
System Memory
When a user initiates an application's execution, the
operating system loads the executable code into RAM and transfers
control to the entry point of the executable (the main()
function). Throughout execution, the application may request
more memory from the operating system. The system attempts to
satisfy all such requests by reserving space in RAM. Once
the application terminates and returns control to the operating system,
the system recovers all of the space that it reserved for the application.
Static Memory
The memory that the operating system reserves for the application at
load time is called static memory. Static memory includes
the space allocated for the program instructions, local variables and
local objects. The linker determines the amount of static memory
that the application requires at link time.

The space reserved for the local variables and objects is shared
amongst them whenever possible. The executable allocates
regions of memory for newly defined variables and objects over the
memory that had been allocated for other variables and objects whose
lifetime has ended. The lifetime of each local variable and
object extends from its definition to the closing brace of the code
block within which it has been defined:
// lifetime of a local variable or an object
for (int i = 0; i < 10; i++) {
double x = 0; // lifetime of x starts here
// ...
} // lifetime of x ends here
for (int i = 0; i < 10; i++) {
double y = 4; // lifetime of y starts here
// ...
} // lifetime of y ends here
|
Note that the variable y may occupy the same
physical memory location in RAM as variable x.
This system of organizing memory for the local variables and objects
ensures that application uses RAM as efficiently as possible.
Dynamic Memory
The additional memory that the operating system reserves for the
application during its execution is called dynamic memory.
Dynamic memory is completely separate from the static memory that the
operating system has reserved for the application at load time.
The operating system reserves dynamic memory at run-time and the
application itself allocates and deallocates regions of it.
To keep track of the dynamic memory currently allocated by the
application, we store the address of each region of it in a pointer
variable. We allocate memory for the pointer variable in static
memory and keep it alive for as long as we require access to that
region of dynamic memory.
Consider allocating dynamic memory for an array of n elements as
shown below. We store the address of the array in a pointer to
it, p, in static memory. We allocate the
array itself dynamically and store the data in its elements sequentially
in dynamic memory, starting at address p.

Lifetime
The lifetime of any dynamic variable or object ends when the
application explicitly deallocates the region of dynamic memory
reserved for that variable or object. If the application
does not deallocate the dynamic memory reserved for a variable or
object, its lifetime extends to the end of the application.
<>p>
Unlike variables and objects that have been allocated in static memory,
those in dynamic memory do not go of out scope at the closing brace of
the code block within which they were defined. That is, we must
manage the deallocation of dynamic variable and objects ourselves.
Dynamic Allocation
The keyword new followed by
[n] allocates contiguous space
outside static memory for an array of n
elements and returns the address of the start of that array.
A dynamic allocation statement takes the form
pointer = new Type[size];
where Type is the primitive or
compound type of the array's elements.
For example, to allocate dynamic memory for an array of
n Students, we write
int n; // holds the number of students
Student* student = nullptr; // will hold the address of the dynamic array
cout << "How many students? ";
cin >> n;
student = new Student[n]; // allocates space in dynamic memory
|
Initialization to nullptr ensures that
student is not pointing to any valid
address before the operating system allocates the dynamic memory.
Note that the size of the array is a run-time variable and not an
integer constant or constant expression as required for a static
array.
Dynamic Deallocation
The keyword delete followed by
[] and the address of the region of
dynamic memory deallocates memory that has been allocated using
new[].
A dynamic array deallocation takes the form
delete [] pointer;
where pointer holds the address
of the start of the dynamically allocated array.
For example, to deallocate the memory allocated for the array of
n Students
above, we write
delete [] student;
student = nullptr; // optional
|
The nullptr assignment ensures that student is no longer pointing to any valid address.
This optional assignment eliminates the possibility of deleting the original
address more than once, which is a serious error. Moreover,
deleting the nullptr address has no effect and
does not cause an error.
Omitting the brackets in a deallocation expression deallocate the first
element of the array and leaves the other elements unreachable.
Deallocation does not return dynamic memory to the operating system.
Deallocated dynamic memory remains available for subsequent re-allocations.
The operating system only reclaims dynamic memory once the application
has terminated and has transferred control back to the system.
Complete Example
Consider a simple program where the user enters as input the
number of Students, the
program allocates memory for that number of
students, the user enters the data for each student, the program
displays the data received and finally the program terminates:
// Dynamic Memory Allocation
// dynamic.cpp
#include <iostream>
using namespace std;
struct Student {
int no;
char grade[14];
};
int main( ) {
int n;
Student* student = nullptr;
cout << "Enter the number of students : ";
cin >> n;
student = new Student[n];
for (int i = 0; i < n; i++) {
cout << "Student Number: ";
cin >> student[i].no;
cout << "Student Grades: ";
cin >> student[i].grade;
}
for (int i = 0; i < n; i++)
cout << student[i].no << ": " << student[i].grade << endl;
delete [] student;
student = nullptr;
}
|
Memory Issues
Two important issues arise with dynamic memory allocation and deallocation:
- memory leaks
- insufficient memory
Memory Leak
A memory leak occurs when an application loses the address of dynamically
allocated memory that has not deallocated. This occurs if
- a pointer to dynamic memory goes out of scope before
the application has deallocated that memory
- a pointer to dynamic memory changes its value before
the application has deallocated the memory starting at that value
Memory leaks are difficult to find because they usually
do not halt execution immediately. We might only become aware of
their existenceindirectly through gradually slower execution or
incorrect results.
Insufficient Memory
Many platforms have sufficient hardware and operating system software to
support large allocations of dynamic memory. On those platforms
where memory is severly limited, a distinct possibility exists that the
operating system might not provide the amount of dynamic memory requested
by an application.
One way to trap execution failures due to insufficient memory is to insert
the argument (nothrow) after the new keyword. new(nothrow)
returns the nullptr address if a memory
allocation failure occurs. nothrow is
defined in the <new> header file.
For example, we may write
// Memory Allocation Failure
// allocationFailure.cpp
#include <new> // reuired for nothrow
#include <iostream>
using namespace std;
struct Student {
int no;
char grade[14];
};
int main( ) {
int n;
Student* student = nullptr;
cout << "Enter the number of students : ";
cin >> n;
student = new (nothrow) Student[n]; // for nullptr on failure
if (student == nullptr)
cout << "Memory Allocation Failed" << endl;
else {
for (int i = 0; i < n; i++) {
cout << "Student Number: ";
cin >> student[i].no;
cout << "Student Grades: ";
cin >> student[i].grade;
}
for (int i = 0; i < n; i++)
cout << student[i].no << ": " << student[i].grade << endl;
delete [] student;
student = nullptr;
}
}
|
Single Instances
We can allocate dynamic memory for single instances of a primitive or
compound type. The syntax for allocating and deallocating dynamic
memory for single instances is similar to that for
allocating and deallocating arrays.
Allocation
The keyword new
without the brackets allocates dynamic memory for a single
variable or object of the specified type.
An allocation statement takes the form
pointer = new Type;
For example, to store one instance of a Student in dynamic memory, we write
Student* harry = nullptr; // a pointer in static memory
harry = new Student; // an instance of Student in dynamic memory
// we must deallocate harry later
|

Deallocation
The keyword delete without the
brackets deallocates dynamic memory at the address specified.
A deallocation statement takes the form
delete pointer;
delete takes either a pointer that
was returned by new.
For example, to deallocate the memory for harry
that was allocated dynamically above, we write
delete harry;
harry = nullptr; // good programming style
|
Operator Review
The C++ operators that we have covered, including those in the C notes
leading up to this set of notes, are listed in the table below.
The order of evaluation of these operators is important in compound
expressions. A compound expression consists of several sub-expressions
where different orders of evaluation are possible. That is, the
results of evaluating a compound expression depend on the order in which
we evalute its sub-expressions. To ensure unique results for all
compound expressions, the C++ language defines rules of precedence on
the operators in any compound expression. This order is from top
to bottom in the table shown below. The operators associate operands
in each sub-expression from left to right, except as noted in the right
column.
Operator |
Associate
From |
:: |
left to right |
[ ] . -> ++ (postfix) -- (postfix)
|
left to right |
++ (prefix) -- (prefix) + - & !
(all unary)
new new[] delete delete[] (type), type() |
right to left |
.* ->* |
left to right |
* / % |
left to right |
+ - |
left to right |
>> << |
left to right |
< <= > >= |
left to right |
== != |
left to right |
&& |
left to right |
|| |
left to right |
= += -= *= /= %= |
right to left |
?: |
left to right |
, |
left to right |
The scope resolution operator :: has
the highest precedence. Its expression is always evaluated first.
We can change the order of evaluation within any compound expression by
enclosing a sub-expression in parentheses. That is, we use
(sub-expression) to evaluate
sub-expression before applying the
rules of precedence to the compound expression.
For example,
2 + 3 * 5 => 2 + 15 => 17
( 2 + 3 ) * 5 => 5 * 5 => 25
|
Summary
- the memory available to an application consists of static memory and dynamic memory
- static memory lasts the lifetime of the application
- the linker determines the amount of static memory needed at link-time
- the operating system provides dynamic memory to an application at run-time upon request
- the keyword new [] allocates a contiguous region of dynamic memory
and returns the address of the start that memory
- we store the address of dynamic memory in static memory
- delete [] deallocates continguous memory from the specified address
- allocated space must be deallocated within the lifetime of the pointer that holds that address
Exercises
|