Previous Page TOC Next Page



- 18 -
Allocating Memory


static


What You'll Learn


This unit explores the memory-management power of Visual C++. Storing data in variables is extremely important but variables are not the only way to store data in memory. Through the techniques that you learn in this unit, you will be storing data in unnamed memory locations. You won't have to keep track of memory addresses. You'll access the memory through pointers.

Some of the pointer discussion up to this point might have seemed, well, pointless. Visual C++ pointers are far from pointless, however. Without pointers, you would not be able to write the kinds of programs that the commercial software developers write all the time. Named variables are simply too limiting to do everything you need to do when writing programs such as word processors. Variables are practical and needed, as you've seen throughout this book, but they can't handle all data requirements alone.

The most important thing to realize about the material in this unit is that you will soon be able to request memory any time your program wants extra memory. When your program is through with that memory, your program can put the data right back where it found it and give the memory back to the system to dole out later or to give to other tasks.

Dynamic Memory




Learn what dynamic memory is and how to control it.

definition

The heap is your PC's unused memory.

The heap is your computer's leftover memory. In other words, the heap would be any memory left after the following tasks consume their share:

How much memory will be left? Under Windows the answer is a lot! With Windows, you have not only the memory that is your RAM, but also memory invented by Windows and its swap files (called virtual memory). Typically, that means megabytes. This compares with a very limited amount that is available for local variables (often limited to say 30KB).



With QuickWin, you are not writing Windows programs but you still have access to all that Windows memory. A QuickWin program is really an emulation of a DOS programs that is normally limited to a miserly 640KB. However, the programs in this book are tiny and under DOS would only need a few kilobytes in which to run, so they would still not be affected if you built them with a compiler that ran DOS programs.

Think for a moment about the implications of this discussion. First of all, you might not even think it matters to your Visual C++ program whether there is a little or a lot of memory left. After all, according to the previous list, there's already room for your Visual C++ program and its data. Why would a Visual C++ program need more memory from this free area called the heap? You might have a 16MB PC, but your customers might be putting up with a 4MB machine. How could you write a program to run on both machines, taking advantage of the extra memory if it was there?

C++ uses the heap to provide dynamic memory allocation. No program in this book has used heap memory yet. As a clue, if a program contains only variables and pointers to variables, the program does not use the heap.



More Memory Won't Solve Anything

As memory grows and as operating environments continue to provide more and more memory, your memory problems will not be solved. It seems that ample memory today is not enough for tomorrow (the same holds true for processor speeds).

As memory grows, the programs we use get bigger, we use more windowed operating environments, and we're more likely to connect to a network of some kind. As your memory increases, you'll begin to load more and more programs at once within your windowed environment, printing a word processor document while calculating a spreadsheet. The more memory we get, the more we use it.

You should not think of your PC as a stand-alone, one-task machine. Even if you never plan to network with another user, you're still going to be loading more and more programs, and you'll be demanding that your computer keep track of more and more data at the same time. These days, a background fax is common. It's easy to receive a fax while writing a Visual C++ program or balancing your checkbook.

The bottom line is that you are going to be adding to that memory crunch with your own programs. When your program defines a variable, that variable will take memory away from another process that could have put that memory to good use. You've got to become more environmentally aware, but that environment is within your PC's memory chips!


All this discussion leads us to the following important point: The heap might be big or small, but it is important. The heap, the available memory after everything running gets what it needs, is constantly being eroded by the operating system and other environments. Luckily, most programs that use memory from the heap put the memory back. That's what this unit is really all about: Use memory when you need it and use as much as you need, but when you are done, put the memory right back on the free heap storage so that other tasks can have access to that memory.

When you begin to use memory as if it is an accordion, growing as you need more memory and shrinking as you put memory back, you'll be optimizing your computer's memory use. Your programs will be able to give more to Windows when Windows needs it (Windows continually grabs and returns heap memory), and give more to other tasks that you might be running in Windows.

Dynamic Memory is Critical for Software Developers


Programmers can't predict how much memory their users will need. When you write a program to work with a specific amount of data, such as the previous year's temperature readings, you know at programming time how much memory is going to be required and you can define your arrays and variables accordingly. However, when you write a program such as a word processor, you have no idea just how much memory the user will consume. The user doesn't even know until he or she writes a document.

You could reserve a tremendous amount of space ahead of time, in a huge array, and the user could fill only the portion needed. However, most of the time—in fact, probably all of the time—the user will never fill all of your reserved memory. Therefore, when you define more than enough space, you waste too many memory resources that could be better utilized by the user's operating system, network, and windowing environment.

The goal of dynamic memory usage is to use only what the user needs. If the user begins typing a document into a word processor program that is based on the heap, the word processor can begin with very little memory reserved. As the user types more and more, the word processor can grab more memory from the heap. If the user deletes a bunch of pages, that memory can be sent back to the heap so that other programs can use the memory.

Getting the Terms


When your program uses only named variables and arrays to hold data, the program is not taking advantage of dynamic memory. The memory sits there, perhaps being used by other tasks but not by your programs.

Before learning how to work with the heap, you must understand some important terms associated with the heap. Table 18.1 lists the terms most often used.

Table 18.1. Terms associated with the heap.

Term Definition
Allocate To request memory from the heap. Once a program successfully allocates memory for data, the operating system makes sure that no other tasks can access that allocated memory. In other words, when you define heap storage, the available heap shrinks in size that many bytes.
Available heap The amount of memory on the heap at any one time.
Deallocate allocation Releasing heap memory from your program's use and returning the memory to the available heap.
Dynamic memory The process of allocating and deallocating heap memory from within a program.
Free Same as deallocate.
Free heap Same as available heap.
Free store Same as available heap.
Heap management Making sure that you utilize the heap properly, checking for errors when you allocate, and freeing heap memory that you no longer need.
Unallocated heap Same as available heap.

When you read other literature about Visual C++ programming, you'll run across these terms. Despite the long list of terms, the entire process of using the heap boils down to these two steps:

The accordion-like growth and shrinkage of the heap produces dynamic memory allocation. Instead of defining all your variables in advance, you allocate memory dynamically, on the fly, when you need it.

definition

Dynamic means changing, as opposed to static, which means constant.

Many times, a program you write needs a lot of memory, but only for a small portion of the program. For example, a customer processing program might track customers as they purchase items throughout the day. The program runs all day. However, at any one time, the program needs very little memory because, as each customer finishes paying, the program stores that customer's data to the disk. The program does not need to be able to hold more than one customer's structure at any one time until the end of the day when the store manager selects the menu option to produce the evening reports. The day's customer data is then read into memory, statistics are computed, and reports are produced. Only at the end of the day does the program need to allocate a lot of memory. There is simply no reason to keep that memory allocated throughout the day, especially if the PC is connected to a network in which memory is even more dear than on a stand-alone computer.



Local variables are not part of the heap even though they disappear when their block ends as described in Lesson 7. The memory for local variables comes from a section of memory known as the stack, and you can't control the size of the stack in any way. A local variable's value disappears once its block ends, but the memory reserved for that variable does not automatically become available to other tasks in the same way that the heap memory becomes available.



You now know the importance of using dynamic memory. When a program needs memory, it should get that memory from the heap. When the program is finished, the program should free the memory back to the heap. The heap does not replace the named variables that you've seen so far. Often, variables are needed to hold totals, counters, and even data. The heap, however, is a better holding place when you must work with large amounts of data, or varying amounts of data, because the heap can grow and shrink as you use it.



There is no Stop and Type part here due to this section's textual nature.


The Heap Commands




The new command allocates heap memory for your Visual C++ programs while delete deallocates memory by sending unwanted memory back to the heap's control.

From now on, when you think of dynamic memory, forget about the notion of sequential memory. In other words, think of the heap as just a pile of memory locations heaped on top of each other! This analogy is important if you are to use dynamic memory effectively. When you allocate dynamic memory two times in a row, the second chunk of dynamic memory might not be close to the first. You can't predict just where from dynamic memory your next allocation request will come. Likewise, when you deallocate memory, you don't know what Visual C++ will do with that memory. After the memory is deallocated, forget about it because you have no idea where the memory went.

You must keep track of allocated heap space with pointers. You can't name dynamic memory because allocated memory contains no variables to name. Allocated bytes might be located anywhere, and if you store data in that memory and then free that memory back to the heap, those values aren't necessarily still in the heap memory. When it is freed, you must forget all about the memory. Again, the pile of heap memory analogy helps keep your management of dynamic memory better focused. As Figure 18.1 shows, when you allocate from the heap, Visual C++ might go anywhere to get; when you deallocate, you must act as if you don't know where that memory ever came from.

Figure 18.1. Who knows where Visual C++ will grab the next chunk? Who knows where Visual C++ will put it back? Only Visual C++ knows!

So far, this discussion might sound strange, but it is important, so heed its warnings. When you free memory, forget about it. If you need a value from that memory later, don't free it! Some Visual C++ programmers deallocate the heap but expect to still use their pointers to read values they stored on the heap. Those Visual C++ programmers soon find lots of bugs. Always keep in mind that you share the heap with other tasks, especially the operating system. As soon as you free memory, that memory is available for another task to grab immediately, so whatever you put there is usually long gone if you try to read the value later.

Rarely will you allocate one byte from dynamic memory at a time. Doing so would be tremendously inefficient and would take too much work. Usually, you'll allocate a chunk of memory at once and your application will determine how many bytes that chunk should be. For example, if you were writing a word processing program, you wouldn't want to allocate a new byte each time the user typed a new character into the document. Instead, you might allocate 100 or 250 bytes at a time, and then let the user fill that memory up. If the user needs more, allocate another chunk.

Although you don't know the address where Visual C++ will get its next allocation, and although you can't rely on Visual C++'s next allocated memory falling directly behind the memory you last allocated, you can rely on Visual C++ to give you contiguous memory within each allocation. Whether you allocate 10 or 100 bytes of memory, Visual C++ might go anywhere on the heap to get that memory, but those 10 or 100 bytes will all appear back-to-back, sequentially in memory. You can rely on the memory being contiguous. As a matter of fact, you have to rely on the memory being contiguous or the heap would not offer much advantage to the programmer.

Figure 18.2 shows you what Visual C++ does when you request eight bytes from the heap. Visual C++ goes to the heap and finds where it wants to grab those eight bytes. When Visual C++ gets the eight bytes, however, those bytes are always together, with the first address of the first byte appearing exactly eight bytes before the address of the last byte.

Figure 18.2. The memory that you allocate will always be contiguous.

If you understand the rest of this paragraph, you will already have garnered a full understanding of the heap: When you allocate heap memory, you also provide a pointer variable. When the allocation is completed, Visual C++ makes sure that the pointer variable points to the first byte of the memory you just allocated. The rest of the allocated memory will appear right behind that address. You end up with a contiguous set of new memory pointed to by a pointer. You then can use array notation or pointer notation on that memory. In other words, even though the allocated memory has no variable name, you can access the memory via the pointer as if it is an array that you defined. Unlike your program's defined arrays, however, this allocated array did not begin taking up memory until you allocated it! Also, this array's memory will go back to the system as soon as you deallocate the array, even though your program will not be over yet.

The new command allocates memory. The delete command frees memory. new always returns a pointer. Look again at Figure 18.2 and you'll see that the heap memory is shown pointed to by a pointer. Therefore, when you execute new, you must supply a pointer that new can make point to the allocated memory.

new will always allocate data based on the number of bytes that you want allocated. You don't allocate characters, you don't allocate integers, and you don't allocate floating-point values, but you do allocate bytes. The end result is that you'll eventually store characters, integers, and floating-point values on the heap, but when you execute new, you must tell new how many bytes to allocate before new can properly allocate.

There are two formats of the new command that your Visual C++ programs will execute:


aPointerVar = new dataType;


anotherPointerVar = new dataType[num];

The first version allocates a piece of memory big enough to hold a single dataType. The second version allocates a piece of memory to hold an array of dataType.

As you can see, you must assign the return value of new to a pointer variable. The pointer variable must be the same data type as the kind of data you want to store on the heap. If you want to store a double value on the heap, you must assign new to a double pointer. If you want to store an array of 18 double values on the heap, you still need a double pointer:


double *dPtr = new double;

double *dPtrs = new double[18];

Recall that a pointer variable is just like an array in that once you have allocated your storage to a pointer, you can treat it using the array syntax or the pointer syntax.

Let's look at a specific new example. If you are trying to allocate 50 integers on the heap, you first need an integer pointer. The following definition defines an integer pointer:


int * hPtr;    // Defines a pointer to integers

The following new command call defines 50 integers on the heap and assigns the hPtr pointer to point to the first of those integers:


hPtr = new int [50];

The format of new makes sense. This command tells new to allocate 50 integers. How many bytes does this new allocate? The answer is 50 * sizeof(int). In Visual C++, that's exactly 100 bytes because 50 integers consume 100 bytes at two bytes each. The data type following new tells Visual C++ what kind of data you want allocated, and the value inside the brackets tells Visual C++ how many of those values to allocate. If you omit the bracketed value, Visual C++ allocates a single value on the heap.

After you allocate memory, you can treat that memory as if it is an array. That's the good news! After you execute new, the allocated memory is just like an array, and you can treat the memory as if it is an array because an array is nothing more than a pointer to a list of contiguous data, which is what you get from new.

When you are done with the memory, be sure to put the memory back on the heap. Other tasks will then have access to your freed memory. There are two formats of delete:


delete aPointerVar; // delete


delete [] anArrayVar;

To free the memory that was allocated earlier, you pass hPtr to delete like this:


delete hPtr;   // Deallocates the allocated memory


delete [] dPtr; // Deallocates an array of allocated memory

It is up to you as the programmer to ensure that you match an array new[] with an array delete[].

When you free the memory, keep these things in mind:

  1. You can never use that memory again through the pointer that you had originally allocated with.

  2. hPtr is still visible but its value is meaningless. Although the actual address in hPtr will not change, keep in mind that when Visual C++ deallocates memory, you must assume that you don't know where that memory will be put back. Right after your delete command, your operating system, network, or windowing environment might have decided to allocate some dynamic memory. If so, another task might be using the address held in hPtr.

  3. hPtr is still an active visible variable, at least until its block ends. You can use hPtr for an integer pointer to hold addresses of other integers, and you can use hPtr for another allocation. delete frees the allocated memory but does not change the life of hPtr.

  4. You must use the correct version of delete. C++ will not report an error. It can't track how you have manipulated memory pointers.

  5. It is sensible to set to zero a pointer that is invalid. This means you can easily test to see whether a pointer points to valid memory elsewhere in your code. You can't examine a pointer value in any other way to decide whether it is valid. There is another advantage: If you have difficulty in your code keeping track of whether a pointer is valid, you can safely delete a zeroed pointer and no damage to memory will occur.

  6. You must never delete memory twice. Your program, and possibly Windows, will go very wrong.



When you allocate memory with new, Visual C++ keeps track through an internal table of how many bytes you allocated. When you free the pointer pointing to those bytes, Visual C++ remembers exactly how many bytes it allocated to that pointer and deallocates exactly that many bytes.

You can allocate your own structure data with dynamic memory. If you had defined a structure named Customer, you could define as many heap locations for as many Customer structures as your program needs. The following new defines 150 Customer structures on the heap, assuming that custPtr is a defined pointer to your structure:


custPtr= new Customer [150];

The struct keyword can be added after the new, but it is optional. After you allocate structures on the heap, you're left with a pointer to a structure, not a structure variable. Therefore, you must use the structure pointer operator, ->, not the dot operator to store and retrieve members within that structure. The following assignments would store four values in four of the Customer members:


custPtr->purchase = 65.27;

strcpy(custPtr->name, "Sam Kane");

custPtr->balance += custPtr->purchase;

custPtr->code = 'X';

Listing 18.1 contains a program that totals the checks written by the user in the previous month. Instead of defining a big array, the program allocates memory based on the number of checks the user actually wrote.



new dynamically allocates memory when your program needs it, and delete sends that memory back to the heap.

Input Listing 18.1. Allocate an array based on the user's input.

  1:// Filename: ALCHECK.CPP

  2:// Asks the user for the number of checks written

  3:// last month, then allocates that many floating-point

  4:// values. The user then enters each check into the

  5:// allocated array and the program prints the total

  6:// after all checks are entered.

  7:#include <iostream.h>

  8:

  9:int   HowMany();

 10:void  GetChecks(int noOfChecks, float * theChecks);

 11:float GetTotal(int noOfChecks, const float * theChecks);

 12:

 13:void main()

 14:{

 15:  int noOfChecks;

 16:  float total;

 17:  float * theChecks;  // Will point to allocated memory

 18:

 19:  cout << "** Monthly Checkbook Program **" << endl << endl;

 20:

 21:  noOfChecks = HowMany();          // Ask the user how many checks

 22:

 23:  // Allocate the memory, 1 float per check

 24:  theChecks = new float [noOfChecks];

 25:  GetChecks(noOfChecks, theChecks);          // Get the values

 26:  total = GetTotal(noOfChecks, theChecks);   // Add them up

 27:  cout.precision(2);

 28:  cout.setf(ios::showpoint);

 29:  cout.setf(ios::fixed);

 30:  cout << endl << endl << "Your total was $" << total

 31:       << " for the month." << endl;

 32:  delete [] theChecks;

 33:  return;

 34:}

 35://***********************************************************

 36:int HowMany()

 37:{

 38:  int ans;  // To hold the cin value

 39:  cout << "How many checks did you write last month? ";

 40:  cin >> ans;

 41:  return (ans);

 42:}

 43://***********************************************************

 44:void GetChecks(int noOfChecks, float * theChecks)

 45:{

 46:  int ctr;

 47:  // No need or vehicle for passing allocated memory. The

 48:  // memory does not go away between functions or blocks.

 49:  cout << endl << "You now must enter the checks, one at a time."

 50:       << endl << endl;

 51:  for (ctr=0; ctr< noOfChecks; ctr++)

 52:  {

 53:    cout << "How much was check " << (ctr+1) << " for? ";

 54:    cin >> theChecks[ctr];  // Store value on the heap

 55:  }

 56:  return;

 57:}

 58://***********************************************************

 59:float GetTotal(int noOfChecks, const float * theChecks)

 60:{

 61:  // Add up the check totals

 62:  int ctr;

 63:  float total = 0.0;

 64:  for (ctr=0; ctr<noOfChecks; ctr++)

 65:  {

 66:    total += theChecks[ctr];

 67:  }

 68:  return total;

 69:}

Output


** Monthly Checkbook Program **

How many checks did you write last month? 6

You now must enter the checks, one at a time.

How much was check 1 for? 17.82

How much was check 2 for? 109.28

How much was check 3 for? 536.49

How much was check 4 for? 9.80

How much was check 5 for? 3.73

How much was check 6 for? 84.08

Your total was $761.20 for the month.

Analysis

As you can see, the only thing new about this program's code is the new on line 24 and the delete on line 32. main() only defines three variables: noOfChecks, total, and theChecks. theChecks is a pointer variable that will be used to point to the allocated memory. After you allocate the floating-point values on line 24, the program treats theChecks as if theChecks is a defined array.

The GetChecks() function uses a for loop in lines 51 through 55 to get its check values. As the user enters check values on line 54, those values go directly to dynamic memory. There is no reason to pass the dynamic memory between functions because the memory is neither local nor global. The dynamic memory is memory that's separate from the variables' memory. As long as you keep track of the pointer to the allocated memory, you can access the allocated memory from anywhere in the code.

If you ever lose track of the value stored in the allocation pointer, you'll never again have access to that allocated memory. You can't use the allocated memory, and you can't free the allocated memory! Be sure that you pass the pointer to dynamic memory between functions because the pointer is the key to getting to the allocated memory.

Check new for Errors




In rare circumstances, there might not be enough memory to allocate. new's return value tells you whether the allocation worked or failed.

There are many reasons that a new might fail. You might have too many device drivers and other programs loaded. You might be requesting far more than new can deliver given your computer's memory limits. Whatever the reason, don't execute new without checking its return value for an error.

new returns 0 if new fails. Even if new could allocate 99 percent of your requested memory, the entire allocation process is a failure if you can't allocate every byte that you need to allocate. After an allocation attempt like


hPtr = new float [2500];

hPtr holds one of two values:

To check hPtr for an error, you simply need to compare it against 0 like this:


if (hPtr == 0)

  {

    cout << "The allocation failed.";

    return (ERROR_CODE);

  }

// Rest of program can assume the allocation worked

Given the fact that hPtr contains false (0) if the allocation failed, the following code is a little more efficient (and easier to read for a C++ programmer) because the extra == does not have to be tested:


if (!hPtr)

  {

    cout << "The allocation failed.";

    return (ERROR_CODE);

  }

// Rest of program can assume the allocation worked

If you fail to check for an allocation error and an error does occur, you will be storing data in memory that is not allocated using a zero-based pointer, and the results will be less than satisfactory (such as your computer freezing up at just the wrong moment or the infamous General Protection Exception of Windows).

Local Scope and Dynamic Memory Scope




Keeping track of dynamic memory requires care to avoid nasty bugs.

When using allocated memory with pointers, you need to remember that the allocated memory does not belong to the pointer; it just happens to be at the end of it. You can pass the memory from one pointer to another, and C++ does not mind. However, the operating system does mind. You should always tidy up memory when it is finished with or it will be lost. If the only pointer that owns the allocated memory goes out of scope, the memory will be lost. When a pointer is destroyed, the memory it points to still exists. Also, you can destroy memory pointed to by more than one pointer, and you can check the code of the other pointer and never spot that it is a different pointer deleting the shared memory.

If you think I'm laboring the point, you're right! The biggest cause of program bugs in C++ (perhaps after copying long strings into short string arrays) is getting the management of allocated memory wrong.

Listing 18.2 explores some of the issues of pointers, dynamic memory, and scope.

Input Listing 18.2. The perils of scope

  1:// Filename : JellyB1.CPP

  2:// Demonstration of pointer scope issues

  3://

  4:#include <iostream.h>

  5:#include <string.h>

  6:

  7:struct JellyBean

  8:  {

  9:    int i;

 10:    int j;

 11:    char* str;

 12:  };

 13:

 14:void main()

 15:  {

 16:    JellyBean red = {1,2,0};

 17:    red.str = new char[30];

 18:

 19:    strcpy(red.str,"A red jelly bean");

 20:

 21:    JellyBean anotherRed = red;

 22:

 23:    // But now both red.str and anotherRed

 24:    // point to str

 25:

 26:    cout << anotherRed.i << ", "

 27:         << anotherRed.j << ", "

 28:         << anotherRed.str;

 29:

 30:    strcpy(red.str,"A blue jelly bean");

 31:

 32:    // Is this a surprise?

 33:    cout << endl << "After assignment to red:" << endl;

 34:    cout << anotherRed.i << ", "

 35:         << anotherRed.j << ", "

 36:         << anotherRed.str;

 37:

 38:    delete [] red.str;

 39:    red.str = 0; // Good practise

 40:    // What does anotherRed.str equal?

 41:    //

 42:    // Another small experiement

 43:    //

 44:    {

 45:      char newString[30] = "Oh! no! not the comfy chair!";

 46:      red.str = newString;

 47:    } // newString no longer exists

 48:    {

 49:      char anotherNewString[30] = "No one expects the Spanish Inquisition!";

 50:      cout << endl << red.str;  // red.str should point to newString???

 51:    }

 52:  }

Output


1, 2, A red jelly bean

After assignment to red:

1, 2, A blue jelly bean

No one expects the Spanish Inquisition!

Analysis

Wow! That is one tricky program for such a few lines of code.

In the JellyBean structure declared in lines 7 through 12, there is a pointer to a character string, but no memory allocated. In line 17, we allocate some dynamic memory and use the strcpy function to copy the data from the literal into the dynamic memory. This is normal practice to make sure that the structure owns the memory.

In line 21, the default assignment operator of C++ copies the contents of the structure into anotherRed. The numbers are copied individually, but something nasty happens with the string. It seems all right (the printout from line 28 tells us so), but in line 34 the contents of anotherRed have changed. There is no mention of anotherRed in lines 29 to 33, so how did that happen? The memory allocated to red is shared with anotherRed. The correct way to copy a structure with pointers is as follows:


anotherRed = red; // ignore pointers for the moment

anotherRed.str = new char[strlen(red.str + 1)];

strcpy(anotherRed.str,red.str);

This code does something different. It finds out how much space is required to hold the string using strlen and then allocates a new separate piece of dynamic memory. It then copies the data from one piece of dynamic memory to the other. Now anotherRed is entirely independent of red and unaffected by any code using red alone. Of course, when anotherRed is done being used, the memory for anotherRed.str needs to be deleted.

Going back to the original code, if you accessed anotherRed.str in line 40, there is no telling what data you would get because the memory pointed to by both strings was removed in line 38 with the array delete keyword. In line 39, I set the pointer to zero. This means that I can test to see whether it owns any memory before I use it again.

Lines 42 to 51 are really another program. They are an experiment with scope. The string array newString in line 45 only exists until line 47. In line 46, the red.str pointer stores the address of the characters that newString stores. (Note that because this is an array, not a character pointer, the initialization copies the data into the newString array. It does not point newString to the literal.) In the theory of C++, the data only exists within the local block, although the pointer exists for longer. At line 47, the character array disappears, leaving red.str pointing nowhere. A new local block starts on line 48 and goes to line 51. This makes up anotherNewString. This time, red.str is not assigned the new string. It tries to print out the old value. Surprise! Although nothing at all references anotherNewString, its contents appear in the output of line 50. The trick is that local variables appear on the stack. At the end of a block, the stack is rolled back, throwing away the variables. You can guarantee that the next block will be placed in the same place on the stack, and by making the local declarations identical, I tricked C++ into seeing the other variables.

This program worked. Imagine what the output would be if the second local block declared some floats instead. The output would be garbage. Worse, the stack also is the place where the statement address of a function call is placed so the program can then get back to the calling function. Get a memory pointer wrong and you could destroy that "address" and the program could jump into virtual reality midair.

Warning! The experiment on lines 44 to 51 is just that—an experiment. It is highly dependent on the way Visual C++ looks after local variables. I engineered the coincidence of the strings. You must never assume how the compiler really works, and other compilers might produce different results.

Multiple Allocations




You can allocate more than one group of dynamic memory, just as you can define several arrays. As long as you define more than one pointer, you can allocate more than one chunk of dynamic memory.

In the same program, you could define three pointer variables like this:


char * cPtr;

int * iPtr;

float * fPtr;

Then, you could allocate three different chunks of memory with these news:


cPtr = new char [150];

iPtr = new int [45];

fPtr = new float [188];

To be complete, you'd also want to check the return value of each new just to make sure that all three allocations work before you attempt to store data in dynamic memory.

One of the most powerful data storage routines you can create is an array of pointers with each pointer pointing to dynamic memory. An array of dynamic memory pointers is fairly common in advanced Visual C++ programs. Perhaps the programs need to keep track of several sets of data, with each set being pointed to by a different pointer in the array.

When you allocate several times using an array of pointers, the rest of your program does not get any harder to code than if you had defined a lot of arrays ahead of time. Yet, you gain the memory-saving techniques through dynamic memory usage. The array of pointers gives you a means to step through (via a for loop) several lists of data values.

Listing 18.3 contains a program that tracks the sales people for five cities, with each city having three sales people. Unlike the previous program, it tries to manage memory properly!



An array of pointers lets you store several lists of allocated data on the heap.

Input Listing 18.3. Allocating an array of pointer data.

  1:// Filename: ARRHEAP.CPP

  2:// Allocates an array of heap pointers

  3:#include <iostream.h>

  4:

  5:void  AllMemory(float * cities[5]);

  6:void  GetCity(float * cities[5]);

  7:float CalcCity(float * cities[5]);

  8:void  FreeAll(float * cities[5]);

  9:

 10:void main()

 11:{

 12:  float * cities[5];     // Five city's worth of data

 13:  float avg=0.0;

 14:  AllMemory(cities);

 15:  cout.precision(2);     // Ensure that dollar

 16:  cout.setf(ios::fixed); // amounts display

 17:  cout.setf(ios::showpoint);

 18:

 19:  GetCity(cities);

 20:  avg = CalcCity(cities);// Total each city

 21:  avg /= 15.0F;          // Calculate average from total

 22:  cout << endl << "The average is $" << avg << endl;

 23:

 24:  FreeAll(cities);       // Why not a simple delete[]?

 25:

 26:}

 27://*********************************************************

 28:void AllMemory(float * cities[5])

 29:{

 30:  // Allocate each array's three values

 31:  int ctr;

 32:  for (ctr=0; ctr<5; ctr++)

 33:    { cities[ctr] = new float [3]; }

 34:}

 35://*********************************************************

 36:void GetCity(float * cities[5])

 37:{

 38:  // This function gets the total number

 39:  // of values for each city. Each city has 3

 40:  // salespeople covering the territories.

 41:  int ctr1, ctr2;

 42:  // Use a nested for-loop to get each city's values

 43:  for (ctr1=0; ctr1<5; ctr1++)

 44:    {  cout << "City #" << (ctr1+1) << ":" << endl;

 45:       for (ctr2=0; ctr2<3; ctr2++)

 46:        {

 47:          cout << "What is value #" << (ctr2+1) << "? ";

 48:          cin >> cities[ctr1][ctr2];

 49:        }

 50:    }

 51:}

 52://*********************************************************

 53:float CalcCity(float * cities[5])

 54:{

 55:  // Add up the total sales in each city

 56:  int ctr1, ctr2;

 57:  float totalCity=0.0, total=0.0;

 58:  cout << endl;

 59:  for (ctr1=0; ctr1<5; ctr1++)

 60: {

 61:     for (ctr2=0; ctr2<3; ctr2++)

 62:      {

 63:        float* &tempCity = cities[ctr1];

 64:        totalCity+= tempCity[ctr2];

 65:      }

 66:     cout << "City #" << (ctr1+1) << " total is $"

 67:          << totalCity << endl;

 68:     total += totalCity; // Add to grand total

 69:     totalCity = 0.0;    // Zero for next city

 70:  }

 71:  return total;

 72:}

 73://*********************************************************

 74:void FreeAll(float * cities[5])

 75:{

 76:  // Free each array's three values

 77:  int ctr;

 78:  for (ctr=0; ctr<5; ctr++)

 79:    { delete cities[ctr]; }

 80:  return;

 81:}

Output


City #1:

What is value #1? 434.56

What is value #2? 554.21

What is value #3? 231.78

City #2:

What is value #1? 765.45

What is value #2? 392.12

What is value #3? 439.24

City #3:

What is value #1? 1021.34

What is value #2? 604.54

What is value #3? 375.58

City #4:

What is value #1? 778.09

What is value #2? 605.77

What is value #3? 542.23

City #5:

What is value #1? 435.70

What is value #2? 835.32

What is value #3? 302.34

City #1 total is $1220.55

City #2 total is $1596.81

City #3 total is $2001.46

City #4 total is $1926.09

City #5 total is $1573.36

The average is $554.55

Analysis

The AllMemory() function (lines 28 through 34) allocates all the city dynamic memory, with five cities and three values per city. Each of the three elements in the cities array points to a different set of three floating-point values. The rest of the program lets the user fill these heap values with 15 sales numbers.

The GetCity() function in lines 46 through 50 contains a nested for loop. The outer loop steps through each of the five cities in line 43, and the inner loop in line 45 steps through each of the three sales figures in each city.

Line 47 requires a little more discussion than a typical cin requires. Although we have not covered it previously, you can have arrays of arrays. Compare the code here to that in lines 61 through 65 of CalcCity, which needs the same access. This is an opportunity to use a reference to make the code more understandable. You can't reference an array, but you can reference a pointer. The slightly tricky line 63 says "make me a reference to a float pointer" (the order of the * and the & are very important). The other trick here is that the reference is created and destroyed in every loop. You can only initialize a reference once, and scope comes to the rescue.

When the city values are in memory, CalcCity() (in lines 53 through 72) calculates each city's total sales (printed in lines 70 and 71 at each iteration of the outer loop) and calculates a running grand total of all the city sales.

After main() prints the grand total, the FreeAll() function steps through each of the city addresses, deallocating them before the program ends.



If you don't free your allocated memory, the operating system might free the memory for you when you return from QuickWin. However, if you rely on Windows to do your job, you might as well not go to the trouble of using dynamic memory allocation because your data will remain allocated and unavailable to the rest of the system—at least while your program is running.


Homework



General Knowledge


  1. What is the heap?

  2. What is dynamic memory allocation?

  3. What is the advantage of using the heap over using defined variables?

  4. What command allocates memory?

  5. What command deallocates memory?

  6. What is the return value of new?

  7. How does delete know how many bytes to deallocate?

  8. How do you access heap memory once you allocate it?

  9. Why must you use the -> operator when accessing structure members on the heap?

  10. How does your use of the heap improve memory for other tasks that might need memory?

  11. How can you ensure that new works properly?

  12. What happens to allocated memory values when you call delete?

  13. Does memory allocation require a header file?

  14. True or false: You can name allocated memory.

  15. True or false: An integer variable can point to heap memory.

  16. True or false: If new can't allocate all of the requested memory, at least new allocates as much as possible.

  17. True or false: If you fail to deallocate memory, the PC will free the memory for you when your program terminates.

  18. True or false: When you use new to allocate a chunk of memory, all bytes in that allocated memory will be contiguous.

  19. True or false: When you deallocate an array of pointers' dynamic memory, an array of pointers also goes away.

    Find the Bug


  20. What is wrong with the following new call?

    
    int values;
    
    values = new int [200];

  21. After allocating a structure like


    
    aStructPtr= new AStruct [10];

    Linda tries to store a string value in the structure's member named firstName, like this:


    
    strcpy(aStructPtr.firstName, "Linda");

  22. Please help Linda find her problem with this code.

    Write Code That. . .


  23. Write a new command that allocates 300 characters on the heap pointed to by the pChar character pointer.

  24. Write a delete command that deallocates the pChar pointer you used to allocate in the preceding question.

  25. Write a program to allocate an array of 10 country names. Allocate 15 letters for each country's name on the heap. Be sure to perform error-checking in case the allocation fails. With a for loop, store a different country on the heap in the 10 spaces that you allocate. Print the names in backward order, from the last country name in the list to the first. Then deallocate the list of names before your program terminates.


    Extra Credit


  26. Write a program that stores three parallel arrays on the heap. The first array is to hold your friends' names (in a heap array of no more than 20 characters each). The second array is to hold your friends' age. The third array is to hold your friends' sex in a single character (as M or F). Print a list of your friends and their data, and at the bottom of the list print the average age. Deallocate all the data when you're done.

  27. Rewrite the program from question 25 by using allocated structures instead of parallel arrays.

Previous Page Page Top TOC Next Page