Free Hosting

Dynamic memory allocation with new and delete


All of the variables used up to this point in the tutorial have one thing in common: the variables must be declared at compile time. This leads to two issues: First, it’s difficult to conditionally declare a variable, outside of putting it in an if statement block (in which case it will go out of scope when the block ends). Second, the size of all arrays must be decided upon in advance of the program being run. For example, the following is not legal:
1
2
3
4
5
cout << "How many variables do you want? ";
int nVars;
cin >> nVars;
 
int anArray[nVars]; // wrong!  The size of the array must be a constant
However, there are many cases where it would be useful to be able to size or resize arrays while the program is being run. 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 chasing 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 number of variables we’ll need and hope that’s enough:
1
2
3
char szName[25]; // let's hope their name is less than 25 chars!
Record asRecordArray[500]; // let's hope there are less than 500 records!
Monster asMonsterArray[20]; // 20 monsters maximum
This is a poor solution for several 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 allocating over twice what we really need! Second, it can lead to artificial limitations and/or buffer overflows. What happens when the user tries to read in 600 records from disk? Because we’ve only allocated 500 spaces, either we have to give the user an error, only read the first 500 records, or (in the worst case where we don’t handle this case at all), we overflow the record buffer and our program crashes.
Fortunately, these problems are easily solved via dynamic memory allocation. Dynamic memory allocation allows us to allocate memory of whatever size we want when we need it.
Dynamically allocating single variables
To allocate a single variable dynamically, we use the scalar (non-array) form of the new operator:
1
int *pnValue = new int; // dynamically allocate an integer
The new operator returns the address of the variable that has been allocated. This address can be stored in a pointer, and the pointer can then be dereferenced to access the variable.
1
2
int *pnValue = new int; // dynamically allocate an integer
*pnValue = 7; // assign 7 to this integer
When we are done with a dynamically allocated variable, we need to explicitly tell C++ to free the memory for reuse. This is done via the scalar (non-array) form of the delete operator:
1
2
delete pnValue; // unallocate memory assigned to pnValue
pnValue = 0;
Note that the delete operator does not delete the pointer — it deletes the memory that the pointer points to!
Dynamically allocating arrays
Declaring arrays dynamically allows us to choose their size while the program is running. To allocate an array dynamically, we use the array form of new and delete (often called new[] and delete[]):
1
2
3
4
int nSize = 12;
int *pnArray = new int[nSize]; // note: nSize does not need to be constant!
pnArray[4] = 7;
delete[] pnArray;
Because we are allocating an array, C++ knows that it should use the array version of new instead of the scalar version of new. Essentially, the new[] operator is called, even though the [] isn’t placed next to the new keyword.
When deleting a dynamically allocated array, we have to use the array version of delete, which is delete[]. This tells the CPU that it needs to clean up multiple variables instead of a single variable.
Note that array access is done the same way with dynamically allocated arrays as with normal arrays. While this might look slightly funny, given that pnArray is explicitly declared as a pointer, remember that arrays are really just pointers in C++ anyway.
One of the most common mistakes that new programmers make when dealing with dynamic memory allocation is to use delete instead of delete[] when deleting a dynamically allocated array. Do not do this! Using the scalar version of delete on an array can cause data corruption or other problems.
Memory leaks
Dynamically allocated memory effectively has no scope. That is, it stays allocated until it is explicitly deallocated or until the program ends. However, the pointers used to access dynamically allocated memory follow the scoping rules of normal variables. This mismatch can create interesting problems.
Consider the following function:
1
2
3
4
void doSomething()
{
    int *pnValue = new int;
}
This function allocates an integer dynamically, but never frees it using delete. Because pointers follow all of the same rules as normal variables, when the function ends, pnValue will go out of scope. Because pnValue is the only variable holding the address of the dynamically allocated integer, when pnValue is destroyed there are no more references to the dynamically allocated memory. This is called a memory leak. As a result, the dynamically allocated integer can not be deleted, and thus can not be reallocated or reused. 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.
Memory leaks can also result if the pointer holding the address of the dynamically allocated memory is reassigned to another value:
1
2
3
int nValue = 5;
int *pnValue = new int;
pnValue = &nValue; // old address lost, memory leak results
It is also possible to get a memory leak via double-allocation:
1
2
int *pnValue = new int;
pnValue = new int; // old address lost, memory leak results
The address returned from the second allocation overwrites the address of the first allocation. Consequently, the first allocation becomes a memory leak!
Null pointers (part II)
Null pointers (pointers set to address 0) are particularly useful when dealing with dynamic memory allocation. A null pointer basically says “no memory has been allocated yet”. This allows us to do things like conditionally allocate memory:
1
2
3
// If pnValue isn't already allocated, allocate it
if (!pnValue)
    pnValue = new int;
Keep in mind that just like normal variables, when a pointer is created, it’s value is undefined. Consequently, it is a good idea to set all pointers that are not used right away to 0:
1
2
int *pnValue = new int;
int *pnOtherValue = 0; // will allocate later
Similarly, when a dynamically allocated variable is deleted, the pointer pointing to it is not zero’d. Consider the following snippet:
1
2
3
4
5
int *pnValue = new int;
delete pnValue; // pnValue not set to 0
 
if (pnValue)
    *pnValue = 5; // will cause a crash
Because pnValue has not been set to 0, the if statement condition evaluates to true, and the program tries to assign 5 to deallocated memory. This almost inevitably will cause a program to crash. It is never a good idea to leave a pointer pointing to deallocated memory. When deallocating memory, set the pointer that has been deallocated to 0 immediately afterward. This helps ensure the program does not try and access memory that has already been deallocated. The above program should be written as:
1
2
3
4
5
6
7
int *pnValue = new int;
*pnValue = 7;
delete pnValue;
pnValue = 0;
 
if (pnValue)
    *pnValue = 5;
Get in the habit of assigning your pointers to 0 both when they are declared (unless assigned to another address), and after they are deleted. It will save you a lot of grief.
Finally, deleting a null pointer has no effect. Thus, there is no need for the following:
1
2
if (pnValue)
    delete pnValue;
Instead, you can just write:
1
delete pnValue;
If pnValue is non-null, the dynamically allocated variable will be deleted. If it is null, nothing will happen.

0 comments:

Blogger Template by Clairvo