C++ has three ways of managing memory for data, depending on the method used to allocate memory: automatic storage, static storage, and dynamic storage, sometimes called the free store or heap. Data objects allocated in these three ways differ from each other in how long they remain in existence. We’ll take a quick look at each type. (C++11 adds a fourth form called thread storage that we’ll discuss briefly in Chapter 9.)
Ordinary variables defined inside a function use automatic storage and are called automatic variables. These terms mean that the variables come into existence automatically when the function containing them is invoked, and they expire when the function terminates. For example, the temp
array in Listing 4.22 exists only while the getname()
function is active. When program control returns to main()
, the memory used for temp
is freed automatically. If getname()
returned the address of temp
, the name
pointer in main()
would be left pointing to a memory location that would soon be reused. That’s one reason you have to use new
in getname()
. Actually, automatic values are local to the block that contains them. A block is a section of code enclosed between braces. So far, all our blocks have been entire functions. But as you’ll see in the next chapter, you can have blocks within a function. If you define a variable inside one of those blocks, it exists only while the program is executing statements inside the block.
Automatic variables typically are stored on a stack. This means that when program execution enters a block of code, its variables are added consecutively to the stack in memory and then are freed in reverse order when execution leaves the block. (This is called a last-in, first-out, or LIFO, process.) So the stack grows and shrinks as execution proceeds.
Static storage is storage that exists throughout the execution of an entire program. There are two ways to make a variable static. One is to define it externally, outside a function. The other is to use the keyword static
when declaring a variable:
static double fee = 56.50;
Under K&R C, you can initialize only static arrays and structures, whereas C++ Release 2.0 (and later) and ANSI C allow you to initialize automatic arrays and structures, too. However, as you may have discovered, some C++ implementations do not yet implement initialization for automatic arrays and structures.
Chapter 9 discusses static storage in more detail. The main point you should note now about automatic and static storage is that these methods rigidly define the lifetime of a variable. Either the variable exists for the entire duration of a program (a static variable) or it exists only while a particular function is being executed (an automatic variable).
The new
and delete
operators provide a more flexible approach than automatic and static variables. They manage a pool of memory, which C++ refers to as the free store or heap. This pool is separate from the memory used for static and automatic variables. As Listing 4.22 shows, new
and delete
enable you to allocate memory in one function and free it in another. Thus, the lifetime of the data is not tied arbitrarily to the life of the program or the life of a function. Using new
and delete
together gives you much more control over how a program uses memory than does using ordinary variables. However, memory management becomes more complex. In a stack, the automatic addition and removal mechanism results in the part of memory in use always being contiguous. But the interplay between new
and delete
can leave holes in the free store, making keeping track of where to allocate new memory requests more difficult.
Pointers are among the most powerful of C++ tools. They are also the most dangerous because they permit computer-unfriendly actions, such as using an uninitialized pointer to access memory or attempting to free the same memory block twice. Furthermore, until you get used to pointer notation and pointer concepts through practice, pointers can be confusing. Because pointers are an important part of C++ programming, they weave in and out of future discussions in this book. This book discusses pointers several more times. The hope is that each exposure will make you more comfortable with them.
This chapter has introduced arrays, structures, and pointers. These can be combined in various ways, so let’s review some of the possibilities, starting with a structure:
struct antarctica_years_end
{
int year;
/* some really interesting data, etc. */
};
We can create variables of this type:
antarctica_years_end s01, s02, s03; // s01, s02, s03 are structures
We can then access members using the membership operator:
s01.year = 1998;
We can create a pointer to such a structure:
antarctica_years_end * pa = &s02;
Provided the pointer has been set to a valid address, we then can use the indirect membership operator to access members:
pa->year = 1999;
We can create arrays of structures:
antarctica_years_end trio[3]; // array of 3 structures
We then can use the membership operator to access members of an element:
trio[0].year = 2003; // trio[0] is a structure
Here, trio
is an array, but trio[0]
is a structure, and trio[0].year
is a member of that structure. Because an array name is a pointer, we also can use the indirect membership operator:
(trio+1)->year = 2004; // same as trio[1].year = 2004;
We can create an array of pointers:
const antarctica_years_end * arp[3] = {&s01, &s02, &s03};
This is starting to look a bit complicated. How can we access data with this array? Well, if arp
is an array of pointers, then arp[1]
must be a pointer, and we can use the indirect membership operator with it to access a member:
std::cout << arp[1]->year << std::endl;
We can create a pointer to such an array:
const antarctica_years_end ** ppa = arp;
Here, arp
is the name of an array; hence, it is the address of its first element. But its first element is a pointer, so ppa
has to be a pointer to a pointer to const antarctica_years_end
, hence the **
. There are several ways you could mess up this declaration. For example, you could omit the const
, forget an *
or two, transpose letters, or otherwise mangle the structure type. Here is an instance for which the C++11 version of auto
is convenient. The compiler is perfectly aware of what type arp
is, so it can deduce the correct type for you:
auto ppb = arp; // C++11 automatic type deduction
In the past, the compiler used its knowledge of the correct type to complain about errors you may have made in the declaration; now it can let its knowledge work for you.
How can you use ppa
to access data? Because ppa
is a pointer to a pointer to a structure, *ppa
is a pointer to a structure, so you can use it with the indirect membership operator:
std::cout << (*ppa)->year << std::endl;
std::cout << (*(ppb+1))->year << std::endl;
Because ppa
points to the first member of arp
, *ppa
is the first member, which is &s01
. So (*ppa)->year
is the year
member of s01
. In the second statement, ppb+1
points to the next element, arp[1]
, which is &s02
. The parentheses are needed to get the correct associations. For example, *ppa->year
would attempt to apply the *
operator to ppa->year
, which fails because the year
member is not a pointer.
Is all this really true? Listing 4.23 incorporates all the preceding statements into a short program.
// mixtypes.cpp -- some type combinations
#include <iostream>
struct antarctica_years_end
{
int year;
/* some really interesting data, etc. */
};
int main()
{
antarctica_years_end s01, s02, s03;
s01.year = 1998;
antarctica_years_end * pa = &s02;
pa->year = 1999;
antarctica_years_end trio[3]; // array of 3 structures
trio[0].year = 2003;
std::cout << trio->year << std::endl;
const antarctica_years_end * arp[3] = {&s01, &s02, &s03};
std::cout << arp[1]->year << std::endl;
const antarctica_years_end ** ppa = arp;
auto ppb = arp; // C++11 automatic type deduction
// or else use const antarctica_years_end ** ppb = arp;
std::cout << (*ppa)->year << std::endl;
std::cout << (*(ppb+1))->year << std::endl;
return 0;
}
2003
1999
1998
1999
The program compiles and works as promised.
Earlier this chapter mentioned the vector
and array
template classes as alternatives to the built-in array. Let’s take a brief look now at how they are used and at some of the benefits of using them.
3.147.74.211