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:
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:
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:
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.
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:
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[]):
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:
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:
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:
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:
// 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:
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:
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:
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:
if (pnValue)
delete pnValue;
Instead, you can just write:
delete pnValue;
If pnValue is non-null, the dynamically allocated variable will be deleted. If it is null, nothing will happen.
6.10 — Pointers and const
|
Index
|
6.8 — Pointers, arrays, and pointer arithmetic
|
6.10 — Pointers and const
Index
6.8 — Pointers, arrays, and pointer arithmetic
New C++ programmers should be notified that there is a fundamental design flaw in C++ (and perhaps any language, allowing explicitly freeing memory) - there is no way to set to null all pointers referencing same memory address, other than knowing/remembering them. Which is affected by “human factors”, and thus can not be reliable. Thats where reference counting and garbage collectors comes in…
That is a great point, Sergk. Thanks for bringing it up.
What Sergk is talking about is the fact that you can have multiple pointers pointing to the same bit of dynamically allocated memory. Let’s say you do something like this:
Now both pointers point to the dynamically allocated memory. However, if you do this:
pnPtr2 is left pointing to deallocated memory! And thus a call like this:
if (pnPtr2) *pnPtr2 = 5;will cause your program to crash, even though it seems like it should be okay.
There are various technique for getting around this behavior (as mentioned, reference counting and garbage collection), some of which C++ support and some of which it doesn’t. From a coding standpoint, perhaps the best solution is simply to avoid having multiple pointers to the same bit of memory, though this isn’t always reasonable.
If by “some of which C++ support and some of which it doesn’t” you mean GC, well in C/C++ you can write what ever you want. In theory. but speaking of Garbage Collectors there is number of them for C++, most widely known (IMHO) is Boehm-Demers-Weiser GC (http://hpl.hp.com/personal/Hans_Boehm/gc/)
Amazon’s affiliate content is not loading properly and causing your website to hang. Please fix the div load order so I don’t have to wait 10 minutes for Amazon to time out and the remainder of the page to display. Otherwise, incredibly helpful content, thank you!
[ The Amazon stuff has been temporarily disabled. -Alex ]
1 “Null pointers (pointers set to address 0) ….”
Cant 0 be address of any memory location?
2 int nSize = 12; int *pnArray = new int[nSize]; [4] = 7; delete[] pnArray;
Why is subscript not required in delete?
like
delete[nSize] pnArray;
3 Typo-
This allows “us to us to” do things like conditionally allocate memory:
Thanks,
Renu
1) Each “chunk” of memory has to have a unique address, otherwise there’d be no way to address that chunk of memory individually. On the 80×86 architecture, the size of that “chunk” is a byte, which means we can address individual bytes. The address of 0 is used to mean “not pointing to anything”.
2) My understanding is that the program/memory system keeps track of how large your arrays are (I am not sure how this happens behind the scenes). In any case, when you delete the array, it already knows how much to delete. Forcing the programmer to specify that amount would be redundant and lead to overspecification errors (eg. what if the programmer allocated 10 bytes but then tried to delete 8?)
3) Thanks, I’ll get that fixed.
In making class Biginteger how we can use dynamic memory allocation
I’m working on an assignment for a course I’m taking and we are to create two classes; Vertex and Polygon, with the Polygon class consisting of an array of Vertices. The array of Vertices is dynamically allocated, and I have two constructors:
class Polygon { private: Vertex* vertices; int numVerts; public: //Constructors Polygon(Vertex vert[], int numVerts){ vertices = vert; this->numVerts = numVerts; } Polygon() { vertices = 0; numVerts = 0; } …The parameterless constructor thus creates an “empty” Polygon. There is however a function add() with which you can add a Vertex to the Polygon. I have written it like this:
void add(Vertex newVert) { Vertex* tmp = new Vertex[numVerts+1]; if(vertices) { tmp = vertices; delete[] vertices; } tmp[numVerts] = newVert; vertices = new Vertex[++numVerts]; vertices = tmp; delete[] tmp; }But if a create a Polygon with the default constructor and then add three Vertices the program crashes. The weird thing though is that the crash doesn’t seem to come until I have come to the last line (delete[] tmp;), but only when I add the third Vertex. The firs two go fine. Any ideas as to why this happens?
Thanks!
/P
I see a couple of things done incorrectly here. First, in your constructor, you assign vertices = vert; You seem to be assuming this will copy the incoming elements, but it won’t. It’ll simply set vertices to point to those elements.
This is asking for trouble. If the user passes in a non-dynamically allocated vertex array, you will end up with the vertex pointer pointing to a local variable that will eventually go out of scope. When it does, and you try to access it, you program will crash.
Instead, it is safer to allocate a new dynamic vertex array inside your constructor and then copy the vertices to it individually (using a for loop). This way you can guarantee that your vertices will persist even if the user passes in vertices that have been non-dynamically allocated.
Second, to see why Add() crashes, simply step through your code when vertices is non-null (which it is when you’ve called the above constructor):
In this case, the if condition is true, so the conditional executes. tmp is set to vertices. Because tmp and vertices are both pointers, this simply does a pointer assignment. It does not copy the vertices! So when you delete vertices, you also delete what tmp is pointing to. Consequently, after the conditional, tmp is pointing to garbage. tmp[numVerts] = newVert tries to write a vertex into unallocated memory. Then later, you delete tmp, which is already pointing to unallocated memory. Trying to delete unallocated memory will always crash your program.
You basically have the right idea, you’ve just assumed that assigning one pointer to another will copy the elements, and it won’t. It’ll just make them point to the same thing.
Your add function should look like this:
void add(Vertex newVert) { Vertex* tmp = new Vertex[numVerts+1]; if(vertices) { // copy your old vertices into temp. This will involve using a for loop // and copying them individually delete[] vertices; } tmp[numVerts] = newVert; vertices = new Vertex[++numVerts]; // you don’t need this vertices = tmp; delete[] tmp; // or this // increment numVerts here }This way, you allocate a new temp array, copy elements from the old array to the new array, delete the old array, and then set the old array to point to the new one.
Thanks!
It now works as it should! Thanks for clearing it up :)
These tutorials are great BTW!