Tải bản đầy đủ - 0 (trang)
Chapter 6. Memory Management: the Stack and the Heap

Chapter 6. Memory Management: the Stack and the Heap

Tải bản đầy đủ - 0trang

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



program needs more space for dynamic unnamed variables, the memory is allocated from the area

called heap. Dynamic variables do not have names, and we refer to them indirectly, through

pointers. We pay for this flexibility with the complexity of dynamic memory management.

In this chapter, we will study C++ techniques for managing the stack and the heap and will learn

the basic techniques of such methods as using name scope, name extent, and dynamic memory

management with pointers. These techniques are the key to the efficient use of system resources. In

inexperienced hands, however, dynamic memory management can lead to system crashes, memory

corruption, and memory leaks (when the system runs out of memory). Some programmers love the

power and the thrill of dynamic memory management. Others prefer to use pointers as little as

possible. Whatever your personal preferences, make sure that you understand the principles of

name management and memory management supported by C++.

Before discussing the issues of dynamic memory management, I'll introduce the concepts of name

scopes and storage classes that are important for the understanding of memory management issues

in C++. After discussing the issues of dynamic memory management, I discuss the techniques of

using external storage¡Xdisk files. Storing data in a disk file enables the program to handle

infinitely large sets of data.

NOTE

Take a deep breath. This is a large chapter. It contains a mixture of important concepts and

practical coding techniques. You cannot become a skillful C++ programmer without mastering

concepts and techniques of memory management and file I/O. However, you can learn the rest of

C++ without becoming an expert in these areas. If you are overloaded with the size and complexity

of this material, move on to the next chapter and come back to this one when you feel you are ready

to learn more.



Name Scope as a Tool for Cooperation

Each programmer-defined name, or identifier, has its lexical scope in the C++ program (often

called just scope).

It is called lexical because it refers to a source code segment where the name is known and can be

used. It is called scope because outside of this code segment the name is either not known or refers

to a different entity. The entities whose names have scopes are the names of programmer-defined

data types, functions, parameters, variables, and labels. The possible uses of the names known

within the scope include definitions, expressions, and function calls.



C++ Lexical Scopes

Lexical scope is a static name characteristic. This means that the scope is defined by the lexical

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (251 of 1187) [8/17/2002 2:57:49 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



structure of the program at compile time rather than by program behavior at run time. There are

four scopes in C++:

ϒΠ



block scope



ϒΠ



function scope



ϒΠ



file scope



ϒΠ



the scope of the whole program



ϒΠ



class scope



ϒΠ



namespace scope.



In this chapter, I will discuss the first four scopes. The other two scopes will be discussed in later

chapters, after the concepts of class and namespace are explained in more detail. The opening and

closing curly braces delimit the block scope. The function scope is also delimited by the opening

and closing curly braces. The difference between the block and the function scope is that the

function has parameters (and their names are known within the scope) and the name. The function

scope is entered during execution when the function is called. The block scope is not called. The

block is executed after the statement that precedes it (if any) is executed. For example, during each

iteration through this for loop, the scope of its unnamed block between braces is entered. When

function getBalance() is called (using its name), the scope of its block is entered. (You will see

this function later in Listing 6.1.)

for (i = 0; i < count; i++)

{ total += getBalance(a[i]); }



// accumulate total



The file scope is delimited by the physical boundaries of the file. It can contain type definitions,

definitions and declarations of variables, and definitions and declarations of functions. Each

program listing I used in previous chapters was a listing of a source file delimited by file

boundaries.

The program scope has no delimiters. Anything belonging to any source file that is part of the

program is within the program scope.



Name Conflicts Within the Same Scope

Name conflicts within a scope are not allowed in C++. A name should be unique within the scope

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (252 of 1187) [8/17/2002 2:57:49 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



where the name is declared. In C, programmer-defined types used to form a separate space. It

means that if a name was used for a type, it could be used for a variable in the same scope. The

compiler (and the maintainer) would figure out from the context whether the name means the type

or the variable.

C++ takes a more-stringent position. All programmer-defined names form a single name space. If a

name is declared in a scope for any purpose, it should be unique in that scope among all the names

declared in the same scope for any purpose. This means that if, for example, count is a name of a

variable, then no type, function, parameter, or another variable can be named count in the same

scope where the variable count is declared.

Similar to most software engineering ideas in language design, this idea aims to improve

readability rather than the ease of writing the program. When the designer (or the maintainer) finds

the name count in the source code, there is no need to figure out which one of the possible

meanings this one has: It has only one meaning within the scope. When the designer (or the

maintainer) wants to add variable count to a scope, he or she has to find out whether this name is

already used in this scope.

The only exception from this rule is label names. They do not conflict with names of variables,

parameters, or types declared or known in the same scope. Since labels are not used that often in

C++ code, this does not result in deterioration of readability. Still, do not use this special

dispensation too much.

The converse of this principle of uniqueness is that the same name can be used in different scopes

without a conflict. This principle decreases the amount of coordination between designers.

Different programmers can work on different parts of the program (different files) and choose

names independently, without communications among team members. Even for the same file, the

need to coordinate names defined in different scopes in the same file would make the job of the

designer (and the maintainer) harder.

Lexical scopes of different program entities (data types, functions, parameters, variables, and

labels) are somewhat different. Type names can be declared in a block, function, or file. They are

known within that block, function, or file from the place of definition until the end of the scope.

They are not known outside of the scope of that block, function, or file. The same is true about the

names of variables. They can be declared in a block, function, or file. They are known from the

place of the definition until the end of the scope.

Parameters can be defined in a function only. They are known from the opening brace of the

function scope until the closing brace of the function. Labels can be defined either in a block or in a

function, but their names are known in the whole function that uses the label and are not known

outside the function.

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (253 of 1187) [8/17/2002 2:57:50 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



C++ function names can be defined in a file, but not in a block and not in another function.

Function names have the program scope; that is, the function name should be unique in the project.

This potential for project-wide name conflicts often makes coordination in the development teams a

headache. The same is true of expanding an existing program during maintenance: Adding new

function names might result in conflicts. Another potential source of trouble related to function

names is integration into the project several libraries that come from different vendors (or from past

projects). Often, the problem might not surface until the files developed separately by different

programmers are linked together quite late in the development cycle.

Listing 6.1 shows a simple example that loads account data, displays data, and computes total of

account balances. For simplicity of the example, I do not load the data set from the keyboard, an

external file, or a database. (We will do that later.) Instead, I use two arrays, num[] and

amounts[], which supply the values of account numbers and account balances. The data is loaded

in the infinite while loop until the sentinel value (-1) is found for the account number; then the

second loop prints account numbers, the third loop prints account balances, and the fourth loop

computes the total of account balances. I use two programmer-defined types, structure Account and

integer synonym Index and function getBalance(), not because they are really needed, but to

illustrate the interaction of scopes. For simplicity's sake, keep the size of the data set very small.

The output of the program is shown on Figure 6-1.



Figure 6-1. Output of code in Listing 6.1.



Example 6.1. Demonstration of lexical scope for types, parameters,variables.

#include

using namespace std;

struct Account {

long num;

double bal; } ;

double getBalance(Account a)

{ double total = a.bal;

return total; }



// global type definition



// total in independent scopes

// return a.bal; is better



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (254 of 1187) [8/17/2002 2:57:50 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



int main()

{

typedef int Index;

// local type definition

Index const MAX = 5;

Index i, count = 0;

// integers in disguise

Account a[MAX]; double total = 0;

// data set, its total

while (true)

// break on the sentinel

{ long num[MAX] = { 800123456, 800123123, 800123333, -1 } ;

double amounts[MAX] = { 1200, 1500, 1800 } ; // data to load

if (num[count] == -1) break;

// sentinel is found

a[count].num = num[count];

// loading data

a[count].bal = amounts[count];

count++; }

cout << " Data is loaded\n\n";

for (i = 0; i < count; i++)

{ long temp = a[i].num;

// temp in independent scopes

cout << temp << endl; }

// display account numbers

for (i = 0; i < count; i++)

{ double temp = a[i].bal;

// temp in independent scopes

cout << temp << endl; }

// display account balances

for (i = 0; i < count; i++)

{ total += getBalance(a[i]); }

// accumulate total for

balances

cout << endl << "Total of balances $" << total << endl;

return 0;

}



NOTE

This program was compiled by the latest version of a 32-bit compiler. This is why there is no need

to indicate that value 800123456 and others are of type long. This program will not compile by an

older 16-bit compiler. In similar code examples in Chapter 5, "Aggregation with ProgrammerDefined Data Types," I used these values with the L suffix ( 800123456L and so on); these examples

will compile with any compiler. C++ programmers should always think about portability issues.

Failure to do so can cause errors. Finding and correcting these errors is frustrating and costly.



Here, type Account has the file scope and is known from the place of its definition to the end of the

source file. Variables of type Account can be defined anywhere in this scope. The use of name

Account for any other purpose in this scope, for example, as the name of an integer, is incorrect.

int Account = 5;



// incorrect use of the name Account



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (255 of 1187) [8/17/2002 2:57:50 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



Type Index has the function scope and is known from the place of its definition until the closing

brace of the main() function. Variables of type Index can be defined in main() but not in another

scope, for example, in function getBalance().

double getBalance(Account a)

{ Index z;

// syntax error: name Index is unknown here

return a.bal; }



Function getBalance() has the program scope. No other object in the program scope can be called

getBalance.



Lexical scope of variable names is most diverse. C++ variables can be defined as:

ϒΠ



Block variables: defined after the opening brace of a block (or in the middle of the

block) and are visible from the place of definition until the end of the block. In Listing 6.1,

block variables are arrays amounts[] and num[] defined in the first loop in main(), variable

temp defined in the second loop in main(), and variable temp defined in the third loop in

main().

ϒΠ



Function variables: similar to the block variables but their scope is a named function

rather than an unnamed block. They are defined in the function body (after the opening brace

or if in the middle) and are visible from the place of definition until the closing brace of the

function. In Listing 6.1, function variables are i, count, MAX, a[], and total defined in

main() and variable total defined in getBalance().

ϒΠ



Function formal parameters: defined in the function header and are visible everywhere in

the function body. This means that the parameter name would conflict with a variable defined

in this function. There is only one formal parameter, a, in function getBalance() in Listing

6.1.

ϒΠ



Global variables: have the file scope¡Xthey are defined in a file outside any function and

are valid from the definition to the end of file. There are no global variables in Listing 6.1; I

will discuss them in the next example.

The names of structure fields are local to the block of the structure definition. This means that they

can be referenced (without further qualifiers) outside of this scope. In Listing 6.1, the field names

num and bal are known only within the definition of structure Account. Hence, bal=10; in main()

is incorrect, because bal is not known in main(). On the other hand, these fields can be referenced

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (256 of 1187) [8/17/2002 2:57:50 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



(using the selector operator) anywhere where variables of type Account are in scope (known,

visible). In Listing 6,1, it is the scope of function main() (where the array a[] of type Account is

defined) and the scope of function getBalance() (where the parameter a of type Account is

defined). Since C++ allows the programmer to define variables in any place within a scope, it is

important to make sure that the name is not used in the scope before it is defined. In Listing 6.1, the

constant MAX should lexically precede the definition of the arrays a[], amounts[], and num[] in

function main().



Using Same Names in Independent Scopes

When names are defined in different scopes they do not conflict with each other (well, with some

exceptions).

The term "different" in the previous paragraph actually needs some clarification. How should the

scopes be related to each other so that the same name could be used in each for different purposes?

Two blocks whose scopes do not intersect (do not have common statements) are different.

Moreover, they are independent from each other. For example, two unnamed blocks that follow

each other (directly or indirectly) in the file or in the function scope are independent and can define

and then use the same name for totally different purposes. The names defined in independent

scopes will not conflict with each other.

In Listing 6.1, the name temp is used in two loops in function main(). Actually, there is no need to

use local variables in these loops: The fields of array elements could be displayed directly.

However, using these variables illustrates the concept of scope well. Since each of these loops has

its own set of scope braces, these uses of name temp refer to different variables, do not conflict

with each other, and do not require coordination of their use.

The same is true about function blocks that define variables or parameters using the same name.

For example, variable total is defined both in getBalance() and in main(). Again, function

getBalance() could do its job without using a local variable, but its use illustrates the concept of

scope.

Similarly, the name a is used as a parameter in function getBalance() and as an array in function

main(). Again, when the names are defined in independent scopes, each name is known within its

own scope only; and there is no need to coordinate their use.



Using Same Name in Nested Scopes

The next type of different scopes is related to the concept of nesting. C++ is a block-structured

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (257 of 1187) [8/17/2002 2:57:50 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



language. This means that its scopes can be lexically nested within each other, that is, the braces of

one scope can be totally inside braces of another scope. Notice that different scopes can be either

independent (one scope ends before another starts) or nested (one scope is inside another), but they

cannot intersect.

Most C++ programs use nested scopes. An unnamed block can be nested in another unnamed block

or in a function. An unnamed block cannot be nested in the file scope directly because control

would not be able to reach it¡Xit needs the function header. A function can be nested in the file

scope only; it cannot be nested in another function. For example, in the design below I try to hide

function getBalance() inside main() so that its name would not be in the file scope and hence

would cause no conflict if some other use of the name getBalance. No such luck: This function is

totally nested within function main(), and hence this design is illegal in C++.

int main()

{ double getBalance(Account a)

{ double total = a.bal;

return total; }



// idea is illegal in C++



. . . .

for (i = 0; i < count; i++)

{ total += getBalance(a[i]); }

// accumulate total

cout << endl << "Total of balances $" << total << endl;

return 0; }



In Listing 6.1, the loop bodies are implemented as unnamed blocks. They are nested within the

scope of function main(); the scopes of functions main() and getBalance() are nested within the

source file scope. In a sense, the file scope is nested in the program scope.

The introduction of nested scopes does not change the rules of visibility for variables or types

defined in the outer scope. They are visible in nested scopes. For example, variable count is known

from the place of its definition to the end of function main() regardless of whether function main()

has any nested scopes. Hence, when the unnamed nested block in the first loop in main() refers to

variable count, it is the variable defined in the outer block that is referenced. Similarly, the

elements of array a[] are referenced in nested blocks in all three loops. Variable total is defined

in main() and is referenced in the nested block of the third loop.

On the other hand, variables defined in the nested scope cannot be referenced in the outer scope.

For example, arrays num[] and amounts[] are defined in the block of the first loop in main() and

cannot be used by main() outside of that block. It would be incorrect to write the second loop in

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (258 of 1187) [8/17/2002 2:57:50 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



Listing 6.1 in the following way, referring to num[] in the outer scope.

for (i = 0; i < count; i++)

cout << num[i] << endl;



// num[] is not known



C++ allows a nested scope to define a variable whose name is also defined in an encompassing

scope. This results in the interaction of the names defined in nested scopes. In this case, the entity

defined in the encompassing scope becomes unavailable in the nested scope. When the name is

used inside the nested scope, it refers to the entity defined in this nested scope. Outside of the

nested scope this name would still refer to the entity (variable, type, or parameter) defined in the

outer scope.

To demonstrate the effects of nesting, let us consider Listing 6.2 that shows a modified version of

the code presented in Listing 6.1. Useless code, both local variables temp in the loop bodies in

main() and function getBalance(), is gone. Other useless changes are done for the sake of the

example: variables MAX (actually, it is a constant), count, and array of Account a[] became global

in the file scope, the function printAccounts() was added that prints both account number and

account balance for each account (on a separate line) in array a[]. The indices are defined within

the loops in main(), not in main() itself. The total of balances is displayed and then the program

searches for a particular account number and displays its balance if found. The output of this

version is shown in Figure 6-2.



Figure 6-2. Output of code in Listing 6.2.



Example 6.2. Demonstration of nesting scopes and name overriding.

#include

using namespace std;

struct Account {

long num;

double bal; } ;

const int MAX = 5;



// maximum size of the data set



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (259 of 1187) [8/17/2002 2:57:50 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



int count = 0;

set

Account a[MAX];



// number of elements in data

// global data to be processed



void printAccounts()

{ for (int i = 0; i < count; i++)

// global count

{ double count = a[i].bal;

// local count

cout << a[i].num << " " << count << endl; } }

int main()

{

typedef int Index;

long num[MAX] = { 800123456, 800123123, 800123333, -1 } ;

long number = 800123123; double total = 0;

// outer scope

while (true)

// break it in the sentinel

{ double amounts[MAX] = { 1200, 1500, 1800 } ; // data to load

if (num[count] == -1) break;

// sentinel is found

double number = amounts[count];

// number hides outer number

a[count].num = num[count];

// loading data

a[count].bal = number;

count++; }

cout << " Data is loaded\n\n";

printAccounts();

for (Index i = 0; i < count; i++)

// global count

{ double count = a[i].bal;

total += count;

// local count

if (i == ::count -1)

// global count

cout << "Total of balances $" << total << endl; }

for (Index j = 0; j < count; j++)

if (a[j].num == number)

// outer number, global array

cout <<"Account "<< number <<" has: $" << a[j].bal << endl;

return 0;

}



The scope of global variables is the file where they are defined. Any function in that file can

reference that name (unless the name is hidden), and all these references will refer to the same

global variable. For example, array a[] and variable count in Listing 6.2 are referenced only in

function printAccounts() and in main(), constant MAX is used only in main(). There is no need

to define these names in printAccounts() and in main() to use them. The global definitions are

enough.

In a sense, the scope of global variables is the program scope rather than the file scope. If you

define the name MAX, count, a, or num as a global name in another file in the same program, the

compiler will compile each file individually because the compiler does not check the contents of

other files during the compilation. However, the linker will report duplicate definitions regardless

whether these names are used for the same or for a totally different purpose. For example, a[] and

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (260 of 1187) [8/17/2002 2:57:50 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



could be defined as scalar variables in another file rather than arrays¡Xstill, this duplicate

usage is an error. This is true of global definitions only and applies neither to declarations nor to

nonglobal definitions. We will see examples in a moment.

num[]



Other C++ scopes (function or block scopes) defined in a particular source file are nested in that

global file scope. Hence, global names are visible in functions within the file as are any outer

names visible in nested scopes. If functions have nested scopes themselves, the names of global

variables are still visible in these nested scopes. In Listing 6.2, global arrays a[] and num[] and

index count are all used in the body of the first loop nested in the scope of main(). Again,

existence of nested scopes (of any depth) does not change the visibility of names defined in

encompassing scopes.)

Nested scopes can define variables using the names defined in enclosing scopes (and hence already

known in the nested scopes). When this name is used in the local nested scope (a function in a file,

or a block in a function or another block), the meaning of this reference is the local name. When

this name is used in the enclosing scope, the meaning of the reference is the meaning defined in the

enclosing scope (because the local name cannot be known outside of its scope).

In Listing 6.2, function printAccounts() uses the name count in the loop continuing condition.

This name refers to the global variable count. Within the loop, however, the name count refers to

the variable defined in the loop body, not in the global scope. The nested name overrides the global

name. In addition to overriding names in nested scopes, other terms are name hiding and name

redefining. Notice that the nested name does not have to define a variable of the same type. It can

be anything.

It is not difficult to write function printAccounts() without using the variable count. I

introduced it only to illustrate the concepts of the name scope on a relatively simple example.

Actually, it is impossible to make up an example where reusing a global name is really a necessity.

You can always come up with a local name different from the name in the encompassing scope.

The beauty of the name scope concept is that you do not have to come up with a different name.

You use the name you like, and this name is known in this scope no matter what names are known

in encompassing scopes.

When the nested scope redefines the name defined in an encompassing scope (global or nested in

another scope), the name defined in the encompassing scope becomes unavailable in the nested

scope. Redefining the name from the outer scope signals to the maintainer the intent of the designer

not to use the global name in the local scope.

In Listing 6.2, the body of the first loop in main() defines variable number using the same name

that is defined in the scope of main() itself. This means that when the loop body says number, it

refers to the local variable of type double rather than to the outer variable of type int because the

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (261 of 1187) [8/17/2002 2:57:50 PM]



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Chapter 6. Memory Management: the Stack and the Heap

Tải bản đầy đủ ngay(0 tr)

×