Dynamic memory allocation with new and delete
The need for dynamic memory allocation
C++ supports three basic types of memory allocation, of which you’ve already seen two.
- Static memory allocation happens for static and global variables. Memory for these types of variables is allocated once when your program is run and persists throughout the life of your program.
- Automatic memory allocation happens for function parameters and local variables. Memory for these types of variables is allocated when the relevant block is entered, and freed when the block is exited, as many times as necessary.
- Dynamic memory allocation is the topic of this article.
Both static and automatic allocation have two things in common:
- The size of the variable / array must be known at compile time.
- Memory allocation and deallocation happens automatically (when the variable is instantiated / destroyed).
Most of the time, this is just fine. However, you will come across situations where one or both of these constraints cause problems, usually when dealing with external (user or file) input.
For example, we may want to use a string to hold someone’s name, but we do not know how long their name is until they enter it. Or we may want to read in a number of records from disk, but we don’t know in advance how many records there are. Or we may be creating a game, with a variable number of monsters (that changes over time as some monsters die and new ones are spawned) trying to kill the player.
If we have to declare the size of everything at compile time, the best we can do is try to make a guess the maximum size of variables we’ll need and hope that’s enough:
This is a poor solution for at least four reasons:
First, it leads to wasted memory if the variables aren’t actually used. For example, if we allocate 25 chars for every name, but names on average are only 12 chars long, we’re using over twice what we really need. Or consider the rendering array above: if a rendering only uses 10,000 polygons, we have 20,000 Polygons worth of memory not being used!
Second, how do we tell which bits of memory are actually used? For strings, it’s easy: a string that starts with a \0 is clearly not being used. But what about monster[24]? Is it alive or dead right now? That necessitates having some way to tell active from inactive items, which adds complexity and can use up additional memory.
Third, most normal variables (including fixed arrays) are allocated in a portion of memory called the stack. The amount of stack memory for a program is generally quite small -- Visual Studio defaults the stack size to 1MB. If you exceed this number, stack overflow will result, and the operating system will probably close down the program.
On Visual Studio, you can see this happen when running this program:
Being limited to just 1MB of memory would be problematic for many programs, especially those that deal with graphics.
Fourth, and most importantly, it can lead to artificial limitations and/or array overflows. What happens when the user tries to read in 600 records from disk, but we’ve only allocated memory for a maximum of 500 records? Either we have to give the user an error, only read the 500 records, or (in the worst case where we don’t handle this case at all) overflow the record array and watch something bad happen.
Fortunately, these problems are easily addressed via dynamic memory allocation. Dynamic memory allocation is a way for running programs to request memory from the operating system when needed. This memory does not come from the program’s limited stack memory -- instead, it is allocated from a much larger pool of memory managed by the operating system called the heap. On modern machines, the heap can be gigabytes in size.
Dynamically allocating single variables
To allocate a single variable dynamically, we use the scalar (non-array) form of the new operator:
In the above case, we’re requesting an integer’s worth of memory from the operating system. The new operator creates the object using that memory, and then returns a pointer containing the address of the memory that has been allocated.
Most often, we’ll assign the return value to our own pointer variable so we can access the allocated memory later.
We can then dereference the pointer to access the memory:
If it wasn’t before, it should now be clear at least one case in which pointers are useful. Without a pointer to hold the address of the memory that was just allocated, we’d have no way to access the memory that was just allocated for us!
How does dynamic memory allocation work?
Your computer has memory (probably lots of it) that is available for applications to use. When you run an application, your operating system loads the application into some of that memory. This memory used by your application is divided into different areas, each of which serves a different purpose. One area contains your code. Another area is used for normal operations (keeping track of which functions were called, creating and destroying global and local variables, etc…). We’ll talk more about those later. However, much of the memory available just sits there, waiting to be handed out to programs that request it.
When you dynamically allocate memory, you’re asking the operating system to reserve some of that memory for your program’s use. If it can fulfill this request, it will return the address of that memory to your application. From that point forward, your application can use this memory as it wishes. When your application is done with the memory, it can return the memory back to the operating system to be given to another program.
Unlike static or automatic memory, the program itself is responsible for requesting and disposing of dynamically allocated memory.To allocate space dynamically, use the unary operator new, followed by the type being allocated.
EXAMPLE 1:
Here is a code snippet showing the use of new:
new int; //dynamically allocates an integer type
new double; // dynamically allocates an double type
new int[60];
The above-declared statements are not so useful as the allocated space has no names. But the lines written below are useful:
int * p; // declares a pointer p
p = new int; // dynamically allocate an int for loading the address in p
double * d; // declares a pointer d
d = new double; // dynamically allocate a double and loading the address in p
Here is a simple program showing the concept of dynamic memory allocation:
Example:
#include <iostream>
using namespace std;
int main()
{
double* val = NULL;
val = new double;
*val = 38184.26;
cout << "Value is : " << *val << endl;
delete val;
}
EXAMPLE 2:
Initializing a dynamically allocated variable
When you dynamically allocate a variable, you can also initialize it via direct initialization or uniform initialization (in C++11):
Deleting single variables
When we are done with a dynamically allocated variable, we need to explicitly tell C++ to free the memory for reuse. For single variables, this is done via the scalar (non-array) form of the delete operator:
What does it mean to delete memory?
The delete operator does not actually delete anything. It simply returns the memory being pointed to back to the operating system. The operating system is then free to reassign that memory to another application (or to this application again later).
Although it looks like we’re deleting a variable, this is not the case! The pointer variable still has the same scope as before, and can be assigned a new value just like any other variable.
Note that deleting a pointer that is not pointing to dynamically allocated memory may cause bad things to happen.
Dangling pointers
C++ does not make any guarantees about what will happen to the contents of deallocated memory, or to the value of the pointer being deleted. In most cases, the memory returned to the operating system will contain the same values it had before it was returned, and the pointer will be left pointing to the now deallocated memory.
A pointer that is pointing to deallocated memory is called a dangling pointer. Dereferencing or deleting a dangling pointer will lead to undefined behavior. Consider the following program:
In the above program, the value of 7 that was previously assigned to the allocated memory will probably still be there, but it’s possible that the value at that memory address could have changed. It’s also possible the memory could be allocated to another application (or for the operating system’s own usage), and trying to access that memory will cause the operating system to shut the program down.
Deallocating memory may create multiple dangling pointers. Consider the following example:
There are a few best practices that can help here.
First, try to avoid having multiple pointers point at the same piece of dynamic memory. If this is not possible, be clear about which pointer “owns” the memory (and is responsible for deleting it) and which are just accessing it.
Second, when you delete a pointer, if that pointer is not going out of scope immediately afterward, set the pointer to 0 (or nullptr in C++11). We’ll talk more about null pointers, and why they are useful in a bit.
Rule: Set deleted pointers to 0 (or nullptr in C++11) unless they are going out of scope immediately afterward.
Operator new can fail
When requesting memory from the operating system, in rare circumstances, the operating system may not have any memory to grant the request with.
By default, if new fails, a bad_alloc exception is thrown. If this exception isn’t properly handled (and it won’t be, since we haven’t covered exceptions or exception handling yet), the program will simply terminate (crash) with an unhandled exception error.
In many cases, having new throw an exception (or having your program crash) is undesirable, so there’s an alternate form of new that can be used instead to tell new to return a null pointer if memory can’t be allocated. This is done by adding the constant std::nothrow between the new keyword and the allocation type:
In the above example, if new fails to allocate memory, it will return a null pointer instead of the address of the allocated memory.
Note that if you then attempt to dereference this memory, undefined behavior will result (most likely, your program will crash). Consequently, the best practice is to check all memory requests to ensure they actually succeeded before using the allocated memory.
Because asking new for memory only fails rarely (and almost never in a dev environment), it’s common to forget to do this check!
Null pointers and dynamic memory allocation
Null pointers (pointers set to address 0 or nullptr) are particularly useful when dealing with dynamic memory allocation. In the context of dynamic memory allocation, a null pointer basically says “no memory has been allocated to this pointer”. This allows us to do things like conditionally allocate memory:
Deleting a null pointer has no effect. Thus, there is no need for the following:
Instead, you can just write:
If ptr is non-null, the dynamically allocated variable will be deleted. If it is null, nothing will happen.
Memory leaks
Dynamically allocated memory stays allocated until it is explicitly deallocated or until the program ends (and the operating system cleans it up, assuming your operating system does that). However, the pointers used to hold dynamically allocated memory addresses follow the normal scoping rules for local variables. This mismatch can create interesting problems.
Consider the following function:
This function allocates an integer dynamically, but never frees it using delete. Because pointers variables are just normal variables, when the function ends, ptr will go out of scope. And because ptr is the only variable holding the address of the dynamically allocated integer, when ptr is destroyed there are no more references to the dynamically allocated memory. This means the program has now “lost” the address of the dynamically allocated memory. As a result, this dynamically allocated integer can not be deleted.
This is called a memory leak. Memory leaks happen when your program loses the address of some bit of dynamically allocated memory before giving it back to the operating system. When this happens, your program can’t delete the dynamically allocated memory, because it no longer knows where it is. The operating system also can’t use this memory, because that memory is considered to be still in use by your program.
Memory leaks eat up free memory while the program is running, making less memory available not only to this program, but to other programs as well. Programs with severe memory leak problems can eat all the available memory, causing the entire machine to run slowly or even crash. Only after your program terminates is the operating system able to clean up and “reclaim” all leaked memory.
Although memory leaks can result from a pointer going out of scope, there are other ways that memory leaks can result. For example, a memory leak can occur if a pointer holding the address of the dynamically allocated memory is assigned another value:
This can be fixed by deleting the pointer before reassigning it:
Relatedly, it is also possible to get a memory leak via double-allocation:
The address returned from the second allocation overwrites the address of the first allocation. Consequently, the first allocation becomes a memory leak!
Similarly, this can be avoided by ensuring you delete the pointer before reassigning.
Conclusion
Operators new and delete allow us to dynamically allocate single variables for our programs.
Dynamically allocated memory has no scope and will stay allocated until you deallocate it or the program terminates.
Be careful not to dereference dangling or null pointers.
In the next lesson, we’ll take a look at using new and delete to allocate and delete arrays.
The need for dynamic memory allocation
C++ supports three basic types of memory allocation, of which you’ve already seen two.
- Static memory allocation happens for static and global variables. Memory for these types of variables is allocated once when your program is run and persists throughout the life of your program.
- Automatic memory allocation happens for function parameters and local variables. Memory for these types of variables is allocated when the relevant block is entered, and freed when the block is exited, as many times as necessary.
- Dynamic memory allocation is the topic of this article.
Both static and automatic allocation have two things in common:
- The size of the variable / array must be known at compile time.
- Memory allocation and deallocation happens automatically (when the variable is instantiated / destroyed).
Most of the time, this is just fine. However, you will come across situations where one or both of these constraints cause problems, usually when dealing with external (user or file) input.
For example, we may want to use a string to hold someone’s name, but we do not know how long their name is until they enter it. Or we may want to read in a number of records from disk, but we don’t know in advance how many records there are. Or we may be creating a game, with a variable number of monsters (that changes over time as some monsters die and new ones are spawned) trying to kill the player.
If we have to declare the size of everything at compile time, the best we can do is try to make a guess the maximum size of variables we’ll need and hope that’s enough:
This is a poor solution for at least four reasons:
First, it leads to wasted memory if the variables aren’t actually used. For example, if we allocate 25 chars for every name, but names on average are only 12 chars long, we’re using over twice what we really need. Or consider the rendering array above: if a rendering only uses 10,000 polygons, we have 20,000 Polygons worth of memory not being used!
Second, how do we tell which bits of memory are actually used? For strings, it’s easy: a string that starts with a \0 is clearly not being used. But what about monster[24]? Is it alive or dead right now? That necessitates having some way to tell active from inactive items, which adds complexity and can use up additional memory.
Third, most normal variables (including fixed arrays) are allocated in a portion of memory called the stack. The amount of stack memory for a program is generally quite small -- Visual Studio defaults the stack size to 1MB. If you exceed this number, stack overflow will result, and the operating system will probably close down the program.
On Visual Studio, you can see this happen when running this program:
Being limited to just 1MB of memory would be problematic for many programs, especially those that deal with graphics.
Fourth, and most importantly, it can lead to artificial limitations and/or array overflows. What happens when the user tries to read in 600 records from disk, but we’ve only allocated memory for a maximum of 500 records? Either we have to give the user an error, only read the 500 records, or (in the worst case where we don’t handle this case at all) overflow the record array and watch something bad happen.
Fortunately, these problems are easily addressed via dynamic memory allocation. Dynamic memory allocation is a way for running programs to request memory from the operating system when needed. This memory does not come from the program’s limited stack memory -- instead, it is allocated from a much larger pool of memory managed by the operating system called the heap. On modern machines, the heap can be gigabytes in size.
Dynamically allocating single variables
To allocate a single variable dynamically, we use the scalar (non-array) form of the new operator:
In the above case, we’re requesting an integer’s worth of memory from the operating system. The new operator creates the object using that memory, and then returns a pointer containing the address of the memory that has been allocated.
Most often, we’ll assign the return value to our own pointer variable so we can access the allocated memory later.
We can then dereference the pointer to access the memory:
If it wasn’t before, it should now be clear at least one case in which pointers are useful. Without a pointer to hold the address of the memory that was just allocated, we’d have no way to access the memory that was just allocated for us!
How does dynamic memory allocation work?
Your computer has memory (probably lots of it) that is available for applications to use. When you run an application, your operating system loads the application into some of that memory. This memory used by your application is divided into different areas, each of which serves a different purpose. One area contains your code. Another area is used for normal operations (keeping track of which functions were called, creating and destroying global and local variables, etc…). We’ll talk more about those later. However, much of the memory available just sits there, waiting to be handed out to programs that request it.
When you dynamically allocate memory, you’re asking the operating system to reserve some of that memory for your program’s use. If it can fulfill this request, it will return the address of that memory to your application. From that point forward, your application can use this memory as it wishes. When your application is done with the memory, it can return the memory back to the operating system to be given to another program.
Unlike static or automatic memory, the program itself is responsible for requesting and disposing of dynamically allocated memory.To allocate space dynamically, use the unary operator new, followed by the type being allocated.
Here is a code snippet showing the use of new:
new int; //dynamically allocates an integer type
new double; // dynamically allocates an double type
new int[60];
The above-declared statements are not so useful as the allocated space has no names. But the lines written below are useful:
int * p; // declares a pointer p
p = new int; // dynamically allocate an int for loading the address in p
double * d; // declares a pointer d
d = new double; // dynamically allocate a double and loading the address in p
Here is a simple program showing the concept of dynamic memory allocation:
Example:
#include <iostream>
using namespace std;
int main()
{
double* val = NULL;
val = new double;
*val = 38184.26;
cout << "Value is : " << *val << endl;
delete val;
}
Initializing a dynamically allocated variable
When you dynamically allocate a variable, you can also initialize it via direct initialization or uniform initialization (in C++11):
Deleting single variables
When we are done with a dynamically allocated variable, we need to explicitly tell C++ to free the memory for reuse. For single variables, this is done via the scalar (non-array) form of the delete operator:
What does it mean to delete memory?
The delete operator does not actually delete anything. It simply returns the memory being pointed to back to the operating system. The operating system is then free to reassign that memory to another application (or to this application again later).
Although it looks like we’re deleting a variable, this is not the case! The pointer variable still has the same scope as before, and can be assigned a new value just like any other variable.
Note that deleting a pointer that is not pointing to dynamically allocated memory may cause bad things to happen.
Dangling pointers
C++ does not make any guarantees about what will happen to the contents of deallocated memory, or to the value of the pointer being deleted. In most cases, the memory returned to the operating system will contain the same values it had before it was returned, and the pointer will be left pointing to the now deallocated memory.
A pointer that is pointing to deallocated memory is called a dangling pointer. Dereferencing or deleting a dangling pointer will lead to undefined behavior. Consider the following program:
In the above program, the value of 7 that was previously assigned to the allocated memory will probably still be there, but it’s possible that the value at that memory address could have changed. It’s also possible the memory could be allocated to another application (or for the operating system’s own usage), and trying to access that memory will cause the operating system to shut the program down.
Deallocating memory may create multiple dangling pointers. Consider the following example:
There are a few best practices that can help here.
First, try to avoid having multiple pointers point at the same piece of dynamic memory. If this is not possible, be clear about which pointer “owns” the memory (and is responsible for deleting it) and which are just accessing it.
Second, when you delete a pointer, if that pointer is not going out of scope immediately afterward, set the pointer to 0 (or nullptr in C++11). We’ll talk more about null pointers, and why they are useful in a bit.
Rule: Set deleted pointers to 0 (or nullptr in C++11) unless they are going out of scope immediately afterward.
Operator new can fail
When requesting memory from the operating system, in rare circumstances, the operating system may not have any memory to grant the request with.
By default, if new fails, a bad_alloc exception is thrown. If this exception isn’t properly handled (and it won’t be, since we haven’t covered exceptions or exception handling yet), the program will simply terminate (crash) with an unhandled exception error.
In many cases, having new throw an exception (or having your program crash) is undesirable, so there’s an alternate form of new that can be used instead to tell new to return a null pointer if memory can’t be allocated. This is done by adding the constant std::nothrow between the new keyword and the allocation type:
In the above example, if new fails to allocate memory, it will return a null pointer instead of the address of the allocated memory.
Note that if you then attempt to dereference this memory, undefined behavior will result (most likely, your program will crash). Consequently, the best practice is to check all memory requests to ensure they actually succeeded before using the allocated memory.
Because asking new for memory only fails rarely (and almost never in a dev environment), it’s common to forget to do this check!
Null pointers and dynamic memory allocation
Null pointers (pointers set to address 0 or nullptr) are particularly useful when dealing with dynamic memory allocation. In the context of dynamic memory allocation, a null pointer basically says “no memory has been allocated to this pointer”. This allows us to do things like conditionally allocate memory:
Deleting a null pointer has no effect. Thus, there is no need for the following:
Instead, you can just write:
If ptr is non-null, the dynamically allocated variable will be deleted. If it is null, nothing will happen.
Memory leaks
Dynamically allocated memory stays allocated until it is explicitly deallocated or until the program ends (and the operating system cleans it up, assuming your operating system does that). However, the pointers used to hold dynamically allocated memory addresses follow the normal scoping rules for local variables. This mismatch can create interesting problems.
Consider the following function:
This function allocates an integer dynamically, but never frees it using delete. Because pointers variables are just normal variables, when the function ends, ptr will go out of scope. And because ptr is the only variable holding the address of the dynamically allocated integer, when ptr is destroyed there are no more references to the dynamically allocated memory. This means the program has now “lost” the address of the dynamically allocated memory. As a result, this dynamically allocated integer can not be deleted.
This is called a memory leak. Memory leaks happen when your program loses the address of some bit of dynamically allocated memory before giving it back to the operating system. When this happens, your program can’t delete the dynamically allocated memory, because it no longer knows where it is. The operating system also can’t use this memory, because that memory is considered to be still in use by your program.
Memory leaks eat up free memory while the program is running, making less memory available not only to this program, but to other programs as well. Programs with severe memory leak problems can eat all the available memory, causing the entire machine to run slowly or even crash. Only after your program terminates is the operating system able to clean up and “reclaim” all leaked memory.
Although memory leaks can result from a pointer going out of scope, there are other ways that memory leaks can result. For example, a memory leak can occur if a pointer holding the address of the dynamically allocated memory is assigned another value:
This can be fixed by deleting the pointer before reassigning it:
Relatedly, it is also possible to get a memory leak via double-allocation:
The address returned from the second allocation overwrites the address of the first allocation. Consequently, the first allocation becomes a memory leak!
Similarly, this can be avoided by ensuring you delete the pointer before reassigning.
Conclusion
Operators new and delete allow us to dynamically allocate single variables for our programs.
Dynamically allocated memory has no scope and will stay allocated until you deallocate it or the program terminates.
Be careful not to dereference dangling or null pointers.
In the next lesson, we’ll take a look at using new and delete to allocate and delete arrays.
Stack vs Heap Memory Allocation
Memory in a C/C++ program can either be allocated on stack or heap.
Stack Allocation : The allocation happens on contiguous blocks of memory. We call it stack memory allocation because the allocation happens in function call stack. The size of memory to be allocated is known to compiler and whenever a function is called, its variables get memory allocated on the stack. And whenever the function call is over, the memory for the variables is deallocated. This all happens using some predefined routines in compiler. Programmer does not have to worry about memory allocation and deallocation of stack variables.
Heap Allocation : The memory is allocated during execution of instructions written by programmers. Note that the name heap has nothing to do with heap data structure. It is called heap because it is a pile of memory space available to programmers to allocated and de-allocate. If a programmer does not handle this memory well, memory leak can happen in the program.
Key Differences Between Stack and Heap Allocations
- In a stack, the allocation and deallocation is automatically done by whereas, in heap, it needs to be done by the programmer manually.
- Handling of Heap frame is costlier than handling of stack frame.
- Memory shortage problem is more likely to happen in stack whereas the main issue in heap memory is fragmentation.
- Stack frame access is easier than the heap frame as the stack have small region of memory and is cache friendly, but in case of heap frames which are dispersed throughout the memory so it cause more cache misses.
- Stack is not flexible, the memory size allotted cannot be changed whereas a heap is flexible, and the allotted memory can be altered.
- Accessing time of heap takes is more than a stack.
Comparison Chart:
PARAMETER STACK HEAP
Basic Memory is allocated in a contiguous block. Memory is allocated in any random order.
Allocation and Deallocation Automatic by compiler instructions. Manual by programmer.
Cost Less More
Implementation Hard Easy
Access time Faster Slower
Main Issue Shortage of memory Memory fragmentation
Locality of reference Excellent Adequate
Flexibility Fixed size Resizing is possible
Memory in a C/C++ program can either be allocated on stack or heap.
Stack Allocation : The allocation happens on contiguous blocks of memory. We call it stack memory allocation because the allocation happens in function call stack. The size of memory to be allocated is known to compiler and whenever a function is called, its variables get memory allocated on the stack. And whenever the function call is over, the memory for the variables is deallocated. This all happens using some predefined routines in compiler. Programmer does not have to worry about memory allocation and deallocation of stack variables.
Heap Allocation : The memory is allocated during execution of instructions written by programmers. Note that the name heap has nothing to do with heap data structure. It is called heap because it is a pile of memory space available to programmers to allocated and de-allocate. If a programmer does not handle this memory well, memory leak can happen in the program.
Key Differences Between Stack and Heap Allocations
- In a stack, the allocation and deallocation is automatically done by whereas, in heap, it needs to be done by the programmer manually.
- Handling of Heap frame is costlier than handling of stack frame.
- Memory shortage problem is more likely to happen in stack whereas the main issue in heap memory is fragmentation.
- Stack frame access is easier than the heap frame as the stack have small region of memory and is cache friendly, but in case of heap frames which are dispersed throughout the memory so it cause more cache misses.
- Stack is not flexible, the memory size allotted cannot be changed whereas a heap is flexible, and the allotted memory can be altered.
- Accessing time of heap takes is more than a stack.
Comparison Chart:
PARAMETER | STACK | HEAP |
---|---|---|
Basic | Memory is allocated in a contiguous block. | Memory is allocated in any random order. |
Allocation and Deallocation | Automatic by compiler instructions. | Manual by programmer. |
Cost | Less | More |
Implementation | Hard | Easy |
Access time | Faster | Slower |
Main Issue | Shortage of memory | Memory fragmentation |
Locality of reference | Excellent | Adequate |
Flexibility | Fixed size | Resizing is possible |
No comments:
Post a Comment