Function parameters and variables declared inside a function have, by default, automatic storage duration. They also have local scope and no linkage. That is, if you declare a variable called texas
in main()
and you declare another variable with the same name in a function called oil()
, you’ve created two independent variables, each known only in the function in which it’s defined. Anything you do to the texas
in oil()
has no effect on the texas
in main()
, and vice versa. Also each variable is allocated when program execution enters the innermost block containing the definition, and each fades from existence when execution leaves that block. (Note that the variable is allocated when execution enters the block, but the scope begins only after the point of declaration.)
If you define a variable inside a block, the variable’s persistence and scope are confined to that block. Suppose, for example, that you define a variable called teledeli
at the beginning of main()
. Now suppose you start a new block within main()
and define a new variable, called websight
, in the block. Then, teledeli
is visible in both the outer and inner blocks, whereas websight
exists only in the inner block and is in scope only from its point of definition until program execution passes the end of the block:
int main()
{
int teledeli = 5;
{ // websight allocated
cout << "Hello
";
int websight = -2; // websight scope begins
cout << websight << ' ' << teledeli << endl;
} // websight expires
cout << teledeli << endl;
...
} // teledeli expires
But what if you name the variable in the inner block teledeli
instead of websight
so that you have two variables of the same name, with one in the outer block and one in the inner block? In this case, the program interprets the teledeli
name to mean the local block variable while the program executes statements within the block. We say the new definition hides the prior definition. The new definition is in scope, and the old definition is temporarily out of scope. When the program leaves the block, the original definition comes back into scope (see Figure 9.2).
Listing 9.4 illustrates how automatic variables are localized to the functions or blocks that contain them.
// autoscp.cpp -- illustrating scope of automatic variables
#include <iostream>
void oil(int x);
int main()
{
using namespace std;
int texas = 31;
int year = 2011;
cout << "In main(), texas = " << texas << ", &texas = ";
cout << &texas << endl;
cout << "In main(), year = " << year << ", &year = ";
cout << &year << endl;
oil(texas);
cout << "In main(), texas = " << texas << ", &texas = ";
cout << &texas << endl;
cout << "In main(), year = " << year << ", &year = ";
cout << &year << endl;
return 0;
}
void oil(int x)
{
using namespace std;
int texas = 5;
cout << "In oil(), texas = " << texas << ", &texas = ";
cout << &texas << endl;
cout << "In oil(), x = " << x << ", &x = ";
cout << &x << endl;
{ // start a block
int texas = 113;
cout << "In block, texas = " << texas;
cout << ", &texas = " << &texas << endl;
cout << "In block, x = " << x << ", &x = ";
cout << &x << endl;
} // end a block
cout << "Post-block texas = " << texas;
cout << ", &texas = " << &texas << endl;
}
Here is the output from the program in Listing 9.4:
In main(), texas = 31, &texas = 0012FED4
In main(), year = 2011, &year = 0012FEC8
In oil(), texas = 5, &texas = 0012FDE4
In oil(), x = 31, &x = 0012FDF4
In block, texas = 113, &texas = 0012FDD8
In block, x = 31, &x = 0012FDF4
Post-block texas = 5, &texas = 0012FDE4
In main(), texas = 31, &texas = 0012FED4
In main(), year = 2011, &year = 0012FEC8
Notice that each of the three texas
variables in Listing 9.4 has its own distinct address and that the program uses only the particular variable in scope at the moment, so assigning the value 113
to the texas
in the inner block in oil()
has no effect on the other variables of the same name. (As usual, the actual address values and address format will differ from system to system.)
Let’s summarize the sequence of events. When main()
starts, the program allocates space for texas
and year
, and these variables come into scope. When the program calls oil()
, these variables remain in memory but pass out of scope. Two new variables, x
and texas
, are allocated and come into scope. When program execution reaches the inner block in oil()
, the new texas
passes out of scope (is hidden) because it is superseded by an even newer definition. The variable x
, however, stays in scope because the block doesn’t define a new x
. When execution exits the block, the memory for the newest texas
is freed, and texas
#2 comes back into scope. When the oil()
function terminates, that texas
and x
expire, and the original texas
and year
come back into scope.
You can initialize an automatic variable with any expression whose value will be known when the declaration is reached. The following example shows the variables x
, big
, y
, and z
being initialized:
int w; // value of w is indeterminate
int x = 5; // initialized with a numeric literal
int big = INT_MAX – 1; // initialized with a constant expression
int y = 2 * x; // use previously determined value of x
cin >> w;
int z = 3 * w; // use new value of w
You might gain a better understanding of automatic variables if you look at how a typical C++ compiler implements them. Because the number of automatic variables grows and shrinks as functions start and terminate, the program has to manage automatic variables as it runs. The usual means is to set aside a section of memory and treat it as a stack for managing the flow and ebb of variables. It’s called a stack because new data is figuratively stacked atop old data (that is, at an adjacent location, not at the same location) and then removed from the stack when a program is finished with it. The default size of the stack depends on the implementation, but a compiler typically provides the option of changing the size. The program keeps track of the stack by using two pointers. One points to the base of the stack, where the memory set aside for the stack begins, and one points to the top of the stack, which is the next free memory location. When a function is called, its automatic variables are added to the stack, and the pointer to the top points to the next available free space following the variables. When the function terminates, the top pointer is reset to the value it had before the function was called, effectively freeing the memory that had been used for the new variables.
A stack is a LIFO (last-in, first-out) design, meaning the last variables added to the stack are the first to go. The design simplifies argument passing. The function call places the values of its arguments on top of the stack and resets the top pointer. The called function uses the description of its formal parameters to determine the addresses of each argument. For example, Figure 9.3 shows a fib()
function that, when called, passes a 2-byte int
and a 4-byte long
. These values go on the stack. When fib()
begins execution, it associates the names real
and tell
with the two values. When fib()
terminates, the top-of-stack pointer is relocated to its former position. The new values aren’t erased, but they are no longer labeled, and the space they occupy will be used by the next process that places values on the stack. (Figure 9.3 is somewhat simplified because function calls may pass additional information, such as a return address.)
C originally introduced the register
keyword to suggest that the compiler use a CPU register to store an automatic variable:
register int count_fast; // request for a register variable
The idea was that this would allow faster access to the variable.
Prior to C++11, C++ used the keyword in the same fashion, except that as hardware and compilers developed in sophistication, the hint was generalized to mean that the variable was heavily used and perhaps the compiler could provide some sort of special treatment. With C++11, even that hint is being deprecated, leaving register
as just a way to explicitly identify a variable as being automatic. Given that register
can only be used with variables that would be automatic anyway, one reason to use this keyword is to indicate that you really do want to use an automatic variable, perhaps one with the same name as an external variable. This is the same purpose the original use of auto
served. The more important reason for retaining register
, however, is to avoid invalidating existing code that uses that keyword.
18.191.235.176