Dynamic Memory in C++
Understanding how dynamic memory works in C++ is essential for becoming a competent C++ programmer. Memory in a C++ program is divided into two parts:
Stack: All variables declared inside a function consume stack memory.
Heap: This is the unused memory in a program that can be used for dynamic memory allocation during runtime.
Often, you cannot predict in advance how much memory you need to store specific information in a defined variable; the required memory size can only be determined at runtime.
In C++, you can use special operators to allocate memory on the heap for a given type of variable at runtime, which returns the address of the allocated space. This operator is the new operator.
If you no longer need the dynamically allocated memory space, you can use the delete operator to free the memory previously allocated by the new operator.
The new and delete Operators
Below is the general syntax for using the new operator to dynamically allocate memory for any data type:
new data-type;
Here, data-type can be any built-in data type including arrays, or any user-defined data type including classes or structures. Let's start with built-in data types. For example, we can define a pointer to a double type and request memory, which is allocated at execution time. We can accomplish this using the new operator as follows:
double* pvalue = NULL; // Initialize pointer to null
pvalue = new double; // Request memory for the variable
If the free store is exhausted, memory allocation may fail. It is advisable to check if the new operator returns a NULL pointer and take appropriate action:
double* pvalue = NULL;
if (!(pvalue = new double))
{
cout << "Error: out of memory." << endl;
exit(1);
}
The malloc() function appeared in the C language and still exists in C++, but it is recommended to avoid using malloc() if possible. The main advantage of new over malloc() is that new not only allocates memory but also creates an object.
At any time, when you feel that a dynamically allocated variable is no longer needed, you can use the delete operator to free the memory it occupies, as shown below:
delete pvalue; // Free the memory pointed to by pvalue
The following example uses the above concepts to demonstrate how to use the new and delete operators:
Example
#include <iostream>
using namespace std;
int main ()
{
double* pvalue = NULL; // Initialize pointer to null
pvalue = new double; // Request memory for the variable
*pvalue = 29494.99; // Store value at the allocated address
cout << "Value of pvalue : " << *pvalue << endl;
delete pvalue; // Free the memory
return 0;
}
When the above code is compiled and executed, it produces the following result:
Value of pvalue : 29495
Dynamic Memory Allocation for Arrays
Suppose we want to allocate memory for a character array (a string of 20 characters). We can use the syntax from the example above to dynamically allocate memory for the array as follows:
char* pvalue = NULL; // Initialize pointer to null
pvalue = new char[20]; // Request memory for the variable
To delete the array we just created, the statement is:
delete [] pvalue; // Delete the array pointed to by pvalue
Below is the general syntax for the new operator to allocate memory for multi-dimensional arrays:
One-Dimensional Array
// Dynamically allocate an array of length m
int *array = new int[m];
// Free the memory
delete [] array;
Two-Dimensional Array
int **array;
// Assume the first dimension length is m, the second dimension length is n
// Dynamically allocate space
array = new int*[m];
for (int i = 0; i < m; i++)
{
array[i] = new int[n];
}
// Free the memory
for (int i = 0; i < m; i++)
{
delete [] array[i];
}
delete [] array;
Example of a Two-Dimensional Array
#include <iostream>
using namespace std;
int main()
{
int **array;
int m = 3, n = 4;
// Dynamically allocate space
array = new int*[m];
for (int i = 0; i < m; i++)
{
array[i] = new int[n];
}
// Free the memory
for (int i = 0; i < m; i++)
{
delete [] array[i];
}
delete [] array;
return 0;
}
int **p;
int i, j; // p[4][8]
// Start allocating a 4x8 two-dimensional array
p = new int *[4];
for (i = 0; i < 4; i++) {
p[i] = new int[8];
}
for (i = 0; i < 4; i++) {
for (j = 0; j < 8; j++) {
p[i][j] = j * i;
}
}
// Print data
for (i = 0; i < 4; i++) {
for (j = 0; j < 8; j++) {
if (j == 0) cout << endl;
cout << p[i][j] << "\t";
}
}
// Start freeing the allocated heap
for (i = 0; i < 4; i++) {
delete[] p[i];
}
delete[] p;
return 0;
}
Three-dimensional array
int ***array;
// Assume the first dimension is m, the second dimension is n, the third dimension is h
// Dynamically allocate space
array = new int **[m];
for (int i = 0; i < m; i++) {
array[i] = new int *[n];
for (int j = 0; j < n; j++) {
array[i][j] = new int[h];
}
}
// Free
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
delete[] array[i][j];
}
delete[] array[i];
}
delete[] array;
Three-dimensional array test example:
Example
#include <iostream>
using namespace std;
int main() {
int i, j, k; // p[2][3][4]
int ***p;
p = new int **[2];
for (i = 0; i < 2; i++) {
p[i] = new int *[3];
for (j = 0; j < 3; j++) {
p[i][j] = new int[4];
}
}
// Output p[i][j][k] three-dimensional data
for (i = 0; i < 2; i++) {
for (j = 0; j < 3; j++) {
for (k = 0; k < 4; k++) {
p[i][j][k] = i + j + k;
cout << p[i][j][k] << " ";
}
cout << endl;
}
cout << endl;
}
// Free memory
for (i = 0; i < 2; i++) {
for (j = 0; j < 3; j++) {
delete[] p[i][j];
}
}
for (i = 0; i < 2; i++) {
delete[] p[i];
}
delete[] p;
return 0;
}
Dynamic memory allocation for objects
Objects are no different from simple data types. For example, consider the following code where we use an array of objects to clarify this concept:
Example
#include <iostream>
using namespace std;
class Box {
public:
Box() {
cout << "Constructor called!" << endl;
}
~Box() {
cout << "Destructor called!" << endl;
}
};
int main() {
Box* myBoxArray = new Box[4];
delete[] myBoxArray; // Delete array
return 0;
}
If you want to allocate memory for an array containing four Box objects, the constructor will be called 4 times. Similarly, when these objects are deleted, the destructor will also be called the same number of times (4 times).
When the above code is compiled and executed, it produces the following result:
Constructor called!
Constructor called!
Constructor called!
Constructor called!
Destructor called!
Destructor called!
Destructor called!
Destructor called!